Changeset 549 in code for trunk


Ignore:
Timestamp:
Jun 2, 2021, 6:32:11 PM (4 years ago)
Author:
contact
Message:

Implement CHATHISTORY TARGETS

References: https://github.com/ircv3/ircv3-specifications/pull/450

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/downstream.go

    r547 r549  
    20312031                                return err
    20322032                        }
     2033                case "TARGETS":
     2034                        if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
     2035                                return err
     2036                        }
    20332037                default:
    20342038                        // TODO: support LATEST, AROUND
     
    20932097                                history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], bounds[1], limit)
    20942098                        }
     2099                case "TARGETS":
     2100                        // TODO: support TARGETS in multi-upstream mode
     2101                        targets, err := store.ListTargets(uc.network, bounds[0], bounds[1], limit)
     2102                        if err != nil {
     2103                                dc.logger.Printf("failed fetching targets for chathistory: %v", target, err)
     2104                                return ircError{&irc.Message{
     2105                                        Command: "FAIL",
     2106                                        Params:  []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
     2107                                }}
     2108                        }
     2109
     2110                        batchRef := "history-targets"
     2111                        dc.SendMessage(&irc.Message{
     2112                                Prefix:  dc.srv.prefix(),
     2113                                Command: "BATCH",
     2114                                Params:  []string{"+" + batchRef, "draft/chathistory-targets"},
     2115                        })
     2116
     2117                        for _, target := range targets {
     2118                                dc.SendMessage(&irc.Message{
     2119                                        Tags:    irc.Tags{"batch": irc.TagValue(batchRef)},
     2120                                        Prefix:  dc.srv.prefix(),
     2121                                        Command: "CHATHISTORY",
     2122                                        Params:  []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
     2123                                })
     2124                        }
     2125
     2126                        dc.SendMessage(&irc.Message{
     2127                                Prefix:  dc.srv.prefix(),
     2128                                Command: "BATCH",
     2129                                Params:  []string{"-" + batchRef},
     2130                        })
     2131
     2132                        return nil
    20952133                }
    20962134                if err != nil {
  • trunk/msgstore.go

    r516 r549  
    2222}
    2323
     24type chatHistoryTarget struct {
     25        Name          string
     26        LatestMessage time.Time
     27}
     28
    2429// chatHistoryMessageStore is a message store that supports chat history
    2530// operations.
     
    2732        messageStore
    2833
     34        // ListTargets lists channels and nicknames by time of the latest message.
     35        // It returns up to limit targets, starting from start and ending on end,
     36        // both excluded. end may be before or after start.
     37        ListTargets(network *network, start, end time.Time, limit int) ([]chatHistoryTarget, error)
    2938        // LoadBeforeTime loads up to limit messages before start down to end. The
    3039        // returned messages must be between and excluding the provided bounds.
  • trunk/msgstore_fs.go

    r517 r549  
    77        "os"
    88        "path/filepath"
     9        "sort"
    910        "strings"
    1011        "time"
     
    394395}
    395396
    396 func truncateDay(t time.Time) time.Time {
    397         year, month, day := t.Date()
    398         return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
    399 }
    400 
    401397func (ms *fsMessageStore) LoadLatestID(network *network, entity, id string, limit int) ([]*irc.Message, error) {
    402398        var afterTime time.Time
     
    442438        return history[remaining:], nil
    443439}
     440
     441func (ms *fsMessageStore) ListTargets(network *network, start, end time.Time, limit int) ([]chatHistoryTarget, error) {
     442        rootPath := filepath.Join(ms.root, escapeFilename.Replace(network.GetName()))
     443        root, err := os.Open(rootPath)
     444        if err != nil {
     445                return nil, err
     446        }
     447
     448        // The returned targets are escaped, and there is no way to un-escape
     449        // TODO: switch to ReadDir (Go 1.16+)
     450        targetNames, err := root.Readdirnames(0)
     451        root.Close()
     452        if err != nil {
     453                return nil, err
     454        }
     455
     456        var targets []chatHistoryTarget
     457        for _, target := range targetNames {
     458                // target is already escaped here
     459                targetPath := filepath.Join(rootPath, target)
     460                targetDir, err := os.Open(targetPath)
     461                if err != nil {
     462                        return nil, err
     463                }
     464
     465                entries, err := targetDir.Readdir(0)
     466                targetDir.Close()
     467                if err != nil {
     468                        return nil, err
     469                }
     470
     471                // We use mtime here, which may give imprecise or incorrect results
     472                var t time.Time
     473                for _, entry := range entries {
     474                        if entry.ModTime().After(t) {
     475                                t = entry.ModTime()
     476                        }
     477                }
     478
     479                // The timestamps we get from logs have second granularity
     480                t = truncateSecond(t)
     481
     482                // Filter out targets that don't fullfil the time bounds
     483                if !isTimeBetween(t, start, end) {
     484                        continue
     485                }
     486
     487                targets = append(targets, chatHistoryTarget{
     488                        Name:          target,
     489                        LatestMessage: t,
     490                })
     491        }
     492
     493        // Sort targets by latest message time, backwards or forwards depending on
     494        // the order of the time bounds
     495        sort.Slice(targets, func(i, j int) bool {
     496                t1, t2 := targets[i].LatestMessage, targets[j].LatestMessage
     497                if start.Before(end) {
     498                        return t1.Before(t2)
     499                } else {
     500                        return !t1.Before(t2)
     501                }
     502        })
     503
     504        // Truncate the result if necessary
     505        if len(targets) > limit {
     506                targets = targets[:limit]
     507        }
     508
     509        return targets, nil
     510}
     511
     512func truncateDay(t time.Time) time.Time {
     513        year, month, day := t.Date()
     514        return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
     515}
     516
     517func truncateSecond(t time.Time) time.Time {
     518        year, month, day := t.Date()
     519        return time.Date(year, month, day, t.Hour(), t.Minute(), t.Second(), 0, t.Location())
     520}
     521
     522func isTimeBetween(t, start, end time.Time) bool {
     523        if end.Before(start) {
     524                end, start = start, end
     525        }
     526        return start.Before(t) && t.Before(end)
     527}
Note: See TracChangeset for help on using the changeset viewer.