Legend:
- Unmodified
- Added
- Removed
-
trunk/downstream.go
r315 r319 43 43 "Not enough parameters", 44 44 }, 45 }} 46 } 47 48 func newChatHistoryError(subcommand string, target string) ircError { 49 return ircError{&irc.Message{ 50 Command: "FAIL", 51 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"}, 45 52 }} 46 53 } … … 107 114 for k, v := range permanentDownstreamCaps { 108 115 dc.supportedCaps[k] = v 116 } 117 if srv.LogPath != "" { 118 dc.supportedCaps["draft/chathistory"] = "" 109 119 } 110 120 return dc … … 786 796 }) 787 797 // TODO: RPL_ISUPPORT 798 // TODO: send CHATHISTORY in RPL_ISUPPORT when implemented 788 799 dc.SendMessage(&irc.Message{ 789 800 Prefix: dc.srv.prefix(), … … 826 837 827 838 func (dc *downstreamConn) sendNetworkHistory(net *network) { 839 if dc.caps["draft/chathistory"] { 840 return 841 } 828 842 for target, history := range net.history { 829 843 if ch, ok := net.channels[target]; ok && ch.Detached { … … 1511 1525 Params: []string{upstreamUser, upstreamChannel}, 1512 1526 }) 1527 case "CHATHISTORY": 1528 var subcommand string 1529 if err := parseMessageParams(msg, &subcommand); err != nil { 1530 return err 1531 } 1532 var target, criteria, limitStr string 1533 if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil { 1534 return ircError{&irc.Message{ 1535 Command: "FAIL", 1536 Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"}, 1537 }} 1538 } 1539 1540 if dc.srv.LogPath == "" { 1541 return ircError{&irc.Message{ 1542 Command: irc.ERR_UNKNOWNCOMMAND, 1543 Params: []string{dc.nick, subcommand, "Unknown command"}, 1544 }} 1545 } 1546 1547 uc, entity, err := dc.unmarshalEntity(target) 1548 if err != nil { 1549 return err 1550 } 1551 1552 // TODO: support msgid criteria 1553 criteriaParts := strings.SplitN(criteria, "=", 2) 1554 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" { 1555 return ircError{&irc.Message{ 1556 Command: "FAIL", 1557 Params: []string{"CHATHISTORY", "UNKNOWN_CRITERIA", criteria, "Unknown criteria"}, 1558 }} 1559 } 1560 1561 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1]) 1562 if err != nil { 1563 return ircError{&irc.Message{ 1564 Command: "FAIL", 1565 Params: []string{"CHATHISTORY", "INVALID_CRITERIA", criteria, "Invalid criteria"}, 1566 }} 1567 } 1568 1569 limit, err := strconv.Atoi(limitStr) 1570 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit { 1571 return ircError{&irc.Message{ 1572 Command: "FAIL", 1573 Params: []string{"CHATHISTORY", "INVALID_LIMIT", limitStr, "Invalid limit"}, 1574 }} 1575 } 1576 1577 switch subcommand { 1578 case "BEFORE": 1579 batchRef := "history" 1580 dc.SendMessage(&irc.Message{ 1581 Prefix: dc.srv.prefix(), 1582 Command: "BATCH", 1583 Params: []string{"+" + batchRef, "chathistory", target}, 1584 }) 1585 1586 history := make([]*irc.Message, limit) 1587 remaining := limit 1588 1589 tries := 0 1590 for remaining > 0 { 1591 buf, err := parseMessagesBefore(uc.network, entity, timestamp, remaining) 1592 if err != nil { 1593 dc.logger.Printf("failed parsing log messages for chathistory: %v", err) 1594 return newChatHistoryError(subcommand, target) 1595 } 1596 if len(buf) == 0 { 1597 tries++ 1598 if tries >= 100 { 1599 break 1600 } 1601 } else { 1602 tries = 0 1603 } 1604 copy(history[remaining-len(buf):], buf) 1605 remaining -= len(buf) 1606 year, month, day := timestamp.Date() 1607 timestamp = time.Date(year, month, day, 0, 0, 0, 0, timestamp.Location()).Add(-1) 1608 } 1609 1610 for _, m := range history[remaining:] { 1611 m.Tags["batch"] = irc.TagValue(batchRef) 1612 dc.SendMessage(dc.marshalMessage(m, uc.network)) 1613 } 1614 1615 dc.SendMessage(&irc.Message{ 1616 Prefix: dc.srv.prefix(), 1617 Command: "BATCH", 1618 Params: []string{"-" + batchRef}, 1619 }) 1620 default: 1621 // TODO: support AFTER, LATEST, BETWEEN 1622 return ircError{&irc.Message{ 1623 Command: "FAIL", 1624 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"}, 1625 }} 1626 } 1513 1627 default: 1514 1628 dc.logger.Printf("unhandled message: %v", msg) -
trunk/logger.go
r250 r319 2 2 3 3 import ( 4 "bufio" 4 5 "fmt" 5 6 "os" … … 133 134 } 134 135 } 136 137 func parseMessagesBefore(network *network, entity string, timestamp time.Time, limit int) ([]*irc.Message, error) { 138 year, month, day := timestamp.Date() 139 path := logPath(network, entity, timestamp) 140 f, err := os.Open(path) 141 if err != nil { 142 if os.IsNotExist(err) { 143 return nil, nil 144 } 145 return nil, err 146 } 147 defer f.Close() 148 149 historyRing := make([]*irc.Message, limit) 150 cur := 0 151 152 sc := bufio.NewScanner(f) 153 for sc.Scan() { 154 line := sc.Text() 155 var hour, minute, second int 156 _, err := fmt.Sscanf(line, "[%02d:%02d:%02d] ", &hour, &minute, &second) 157 if err != nil { 158 return nil, err 159 } 160 message := line[11:] 161 // TODO: support NOTICE 162 if !strings.HasPrefix(message, "<") { 163 continue 164 } 165 i := strings.Index(message, "> ") 166 if i == -1 { 167 continue 168 } 169 t := time.Date(year, month, day, hour, minute, second, 0, time.Local) 170 if !t.Before(timestamp) { 171 break 172 } 173 174 sender := message[1:i] 175 text := message[i+2:] 176 historyRing[cur%limit] = &irc.Message{ 177 Tags: map[string]irc.TagValue{ 178 "time": irc.TagValue(t.UTC().Format(serverTimeLayout)), 179 }, 180 Prefix: &irc.Prefix{ 181 Name: sender, 182 }, 183 Command: "PRIVMSG", 184 Params: []string{entity, text}, 185 } 186 cur++ 187 } 188 if sc.Err() != nil { 189 return nil, sc.Err() 190 } 191 192 n := limit 193 if cur < limit { 194 n = cur 195 } 196 start := (cur - n + limit) % limit 197 198 if start+n <= limit { // ring doesnt wrap 199 return historyRing[start : start+n], nil 200 } else { // ring wraps 201 history := make([]*irc.Message, n) 202 r := copy(history, historyRing[start:]) 203 copy(history[r:], historyRing[:n-r]) 204 return history, nil 205 } 206 } -
trunk/server.go
r316 r319 39 39 40 40 type Server struct { 41 Hostname string 42 Logger Logger 43 RingCap int 44 LogPath string 45 Debug bool 41 Hostname string 42 Logger Logger 43 RingCap int 44 HistoryLimit int 45 LogPath string 46 Debug bool 46 47 47 48 db *DB … … 53 54 func NewServer(db *DB) *Server { 54 55 return &Server{ 55 Logger: log.New(log.Writer(), "", log.LstdFlags), 56 RingCap: 4096, 57 users: make(map[string]*user), 58 db: db, 56 Logger: log.New(log.Writer(), "", log.LstdFlags), 57 RingCap: 4096, 58 HistoryLimit: 1000, 59 users: make(map[string]*user), 60 db: db, 59 61 } 60 62 }
Note:
See TracChangeset
for help on using the changeset viewer.