}
// Log is the append-only message log of one chat. It writes one JSON line for
-// each message to main/YYYY-MM-DD.jsonl, named for the local day of the write.
-// It never edits or deletes a line. Each line goes to the operating system
-// before Append returns, so a crash of the process loses no message. An
+// each message to main/YYYY-MM-DD.jsonl, named for the local day of the
+// message. It never edits or deletes a line. Each line goes to the operating
+// system before Append returns, so a crash of the process loses no message. An
// exclusive lock on the lock file makes sure that only one process writes the
// log. After a failed write, the end of the file is not known, so the log
// refuses all writes until it is opened again. The caller serializes all calls.
// Append writes a new message to the log and returns it with its ID, size, and
// date.
func (l *Log) Append(kind, text string) (Message, error) {
+ return l.AppendDated(kind, text, "")
+}
+
+// AppendDated writes a message with date in RFC 3339 format. An empty date uses
+// the clock. The day file uses the local day of the given date.
+func (l *Log) AppendDated(kind, text, date string) (Message, error) {
if l.lock == nil {
- return Message{}, os.ErrClosed
+ return Message{}, fmt.Errorf("append dated message: %w", os.ErrClosed)
}
if l.failure != nil {
return Message{}, fmt.Errorf("log needs a reopen after a failed write: %w", l.failure)
}
+ var err error
now := l.clock()
+ if date == "" {
+ date = now.Format(time.RFC3339Nano)
+ } else {
+ now, err = time.Parse(time.RFC3339Nano, date)
+ if err != nil {
+ return Message{}, fmt.Errorf("parse message date: %w", err)
+ }
+
+ now = now.In(time.Local)
+ }
+
message := Message{
ID: l.index.count,
Kind: kind,
Text: text,
Size: len(text),
- Date: now.Format(time.RFC3339Nano),
+ Date: date,
}
- err := logValidateMessage(message)
+ err = logValidateMessage(message)
if err != nil {
- return Message{}, err
+ return Message{}, fmt.Errorf("validate dated message: %w", err)
}
line, err := json.Marshal(message)
var message Message
err := json.Unmarshal(line, &message)
if err != nil {
- return err
+ return fmt.Errorf("decode log message: %w", err)
}
if message.ID != l.index.count {
return l.index.Append(record)
}
-// Message is one entry of the chat log. ID is the permanent position of the
-// message in the chat, from 0. Size is the UTF-8 byte length of Text. Date is
-// the time of the write in RFC 3339 format.
+// Message is one entry of the chat log. ID is its position in the log, from 0.
+// A rebuild assigns positions in the merged log. Size is the UTF-8 byte length
+// of Text. Date is the message time in RFC 3339 format, supplied at append or
+// taken from the clock.
type Message struct {
ID int `json:"i"`
Kind string `json:"kind"`