~emersion/soju-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH v2] Implement CHATHISTORY BETWEEN

Details
Message ID
<20210327120831.15282-1-hubert@hirtz.pm>
DKIM signature
pass
Download raw message
Patch: +85 -32
---

Implemented BETWEEN as BEFORE or AFTER depending on the order of the
bounds.

Since both bounds are exclusive [0] there is no sane way to name them
other than "1" and "2"...

I did some light testing and found it doesn't break BEFORE.

[0] "... the returned messages MUST be counted starting from and
excluding the first message selector, while finishing on and excluding
the second"

 downstream.go  | 52 ++++++++++++++++++++++++++++++--------------------
 irc.go         | 25 ++++++++++++++++++++++++
 msgstore.go    |  1 +
 msgstore_fs.go | 39 ++++++++++++++++++++++++++-----------
 4 files changed, 85 insertions(+), 32 deletions(-)

diff --git a/downstream.go b/downstream.go
index 32ffd6f..d53f837 100644
--- a/downstream.go
+++ b/downstream.go
@@ -1799,11 +1799,21 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
		if err := parseMessageParams(msg, &subcommand); err != nil {
			return err
		}
		var target, criteria, limitStr string
		if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
		var target, bound1Str, bound2Str, limitStr string
		switch subcommand {
		case "AFTER", "BEFORE":
			if err := parseMessageParams(msg, nil, &target, &bound1Str, &limitStr); err != nil {
				return err
			}
		case "BETWEEN":
			if err := parseMessageParams(msg, nil, &target, &bound1Str, &bound2Str, &limitStr); err != nil {
				return err
			}
		default:
			// TODO: support LATEST, AROUND
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
				Params:  []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
			}}
		}

@@ -1821,21 +1831,25 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
		}
		entity = uc.network.casemap(entity)

		// TODO: support msgid criteria
		criteriaParts := strings.SplitN(criteria, "=", 2)
		if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
		bound1 := parseChathistoryBound(bound1Str)
		if _, isTimestamp := bound1.(time.Time); !isTimestamp {
			// TODO: support msgid criteria
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Unknown criteria"},
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, bound1Str, "Unknown criterion"},
			}}
		}

		timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
		if err != nil {
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"},
			}}
		var bound2 chathistoryBound
		if bound2Str != "" {
			bound2 = parseChathistoryBound(bound2Str)
			if _, isTimestamp := bound2.(time.Time); !isTimestamp {
				// TODO: support msgid criteria
				return ircError{&irc.Message{
					Command: "FAIL",
					Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, bound2Str, "Unknown criterion"},
				}}
			}
		}

		limit, err := strconv.Atoi(limitStr)
@@ -1849,15 +1863,11 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
		var history []*irc.Message
		switch subcommand {
		case "BEFORE":
			history, err = store.LoadBeforeTime(uc.network, entity, timestamp, limit)
			history, err = store.LoadBeforeTime(uc.network, entity, bound1.(time.Time), limit)
		case "AFTER":
			history, err = store.LoadAfterTime(uc.network, entity, timestamp, limit)
		default:
			// TODO: support LATEST, BETWEEN
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
			}}
			history, err = store.LoadAfterTime(uc.network, entity, bound1.(time.Time), limit)
		case "BETWEEN":
			history, err = store.LoadBetweenTime(uc.network, entity, bound1.(time.Time), bound2.(time.Time), limit)
		}
		if err != nil {
			dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
diff --git a/irc.go b/irc.go
index 5aeeb95..645c8dc 100644
--- a/irc.go
+++ b/irc.go
@@ -4,6 +4,7 @@ import (
	"fmt"
	"sort"
	"strings"
	"time"

	"gopkg.in/irc.v3"
)
@@ -609,3 +610,27 @@ func (cm *deliveredCasemapMap) Value(name string) deliveredClientMap {
	}
	return entry.value.(deliveredClientMap)
}

// chathistoryBound can either be a string (msgid) or a time.Time (timestamp).
type chathistoryBound interface{}

// parseChathistoryBound parses the given CHATHISTORY parameter as a bound,
// returns `nil` on failure.
func parseChathistoryBound(param string) chathistoryBound {
	parts := strings.SplitN(param, "=", 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..4f9a11f 100644
--- a/msgstore.go
+++ b/msgstore.go
@@ -27,6 +27,7 @@ type chatHistoryMessageStore interface {

	LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error)
	LoadAfterTime(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)
}

func formatMsgID(netID int64, entity, extra string) string {
diff --git a/msgstore_fs.go b/msgstore_fs.go
index fdeb91c..1ab7bed 100644
--- a/msgstore_fs.go
+++ b/msgstore_fs.go
@@ -238,7 +238,7 @@ func parseMessage(line, entity string, ref time.Time) (*irc.Message, time.Time,
	return msg, t, nil
}

func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, ref time.Time, limit int, afterOffset int64) ([]*irc.Message, error) {
func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, ref time.Time, end time.Time, limit int, afterOffset int64) ([]*irc.Message, error) {
	path := ms.logPath(network, entity, ref)
	f, err := os.Open(path)
	if err != nil {
@@ -265,7 +265,7 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r
		msg, t, err := parseMessage(sc.Text(), entity, ref)
		if err != nil {
			return nil, err
		} else if msg == nil {
		} else if msg == nil || !t.After(end) {
			continue
		} else if !t.Before(ref) {
			break
@@ -294,7 +294,7 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r
	}
}

func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, ref time.Time, limit int) ([]*irc.Message, error) {
func (ms *fsMessageStore) parseMessagesAfter(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 {
@@ -313,6 +313,8 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re
			return nil, err
		} else if msg == nil || !t.After(ref) {
			continue
		} else if !t.Before(end) {
			break
		}

		history = append(history, msg)
@@ -324,12 +326,12 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re
	return history, nil
}

func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) {
func (ms *fsMessageStore) loadBeforeTimeUpTo(network *network, entity string, t time.Time, end time.Time, limit int) ([]*irc.Message, error) {
	history := make([]*irc.Message, limit)
	remaining := limit
	tries := 0
	for remaining > 0 && tries < fsMessageStoreMaxTries {
		buf, err := ms.parseMessagesBefore(network, entity, t, remaining, -1)
	for remaining > 0 && tries < fsMessageStoreMaxTries && end.Before(t) {
		buf, err := ms.parseMessagesBefore(network, entity, t, end, remaining, -1)
		if err != nil {
			return nil, err
		}
@@ -347,13 +349,12 @@ func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time
	return history[remaining:], nil
}

func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) {
func (ms *fsMessageStore) loadAfterTimeUpTo(network *network, entity string, t time.Time, end time.Time, limit int) ([]*irc.Message, error) {
	var history []*irc.Message
	remaining := limit
	tries := 0
	now := time.Now()
	for remaining > 0 && tries < fsMessageStoreMaxTries && t.Before(now) {
		buf, err := ms.parseMessagesAfter(network, entity, t, remaining)
	for remaining > 0 && tries < fsMessageStoreMaxTries && t.Before(end) {
		buf, err := ms.parseMessagesAfter(network, entity, t, end, remaining)
		if err != nil {
			return nil, err
		}
@@ -370,6 +371,22 @@ func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.
	return history, nil
}

func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) {
	return ms.loadBeforeTimeUpTo(network, entity, t, time.Unix(0, 0), limit)
}

func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) {
	return ms.loadAfterTimeUpTo(network, entity, t, time.Now(), limit)
}

func (ms *fsMessageStore) LoadBetweenTime(network *network, entity string, t1 time.Time, t2 time.Time, limit int) ([]*irc.Message, error) {
	if t1.Before(t2) {
		return ms.loadAfterTimeUpTo(network, entity, t1, t2, limit)
	} else {
		return ms.loadBeforeTimeUpTo(network, entity, t1, t2, limit)
	}
}

func truncateDay(t time.Time) time.Time {
	year, month, day := t.Date()
	return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
@@ -401,7 +418,7 @@ func (ms *fsMessageStore) LoadLatestID(network *network, entity, id string, limi
			offset = afterOffset
		}

		buf, err := ms.parseMessagesBefore(network, entity, t, remaining, offset)
		buf, err := ms.parseMessagesBefore(network, entity, t, time.Unix(0, 0), remaining, offset)
		if err != nil {
			return nil, err
		}
--
2.31.0
Reply to thread Export thread (mbox)