2017-03-01 00:31:57 +03:00
|
|
|
// Copyright 2017 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package maintner
|
|
|
|
|
|
|
|
import (
|
2017-03-02 21:50:29 +03:00
|
|
|
"context"
|
2017-03-01 00:31:57 +03:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-03-02 21:50:29 +03:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2017-03-01 00:31:57 +03:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"golang.org/x/build/maintner/maintpb"
|
2017-04-14 01:08:15 +03:00
|
|
|
"golang.org/x/build/maintner/reclog"
|
2017-03-28 02:03:06 +03:00
|
|
|
)
|
|
|
|
|
2017-03-01 00:31:57 +03:00
|
|
|
// A MutationLogger logs mutations.
|
|
|
|
type MutationLogger interface {
|
|
|
|
Log(*maintpb.Mutation) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiskMutationLogger logs mutations to disk.
|
|
|
|
type DiskMutationLogger struct {
|
|
|
|
directory string
|
2017-04-30 00:15:37 +03:00
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
done bool // true after first GetMutations
|
2017-03-01 00:31:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDiskMutationLogger creates a new DiskMutationLogger, which will create
|
|
|
|
// mutations in the given directory.
|
|
|
|
func NewDiskMutationLogger(directory string) *DiskMutationLogger {
|
2017-03-22 20:01:56 +03:00
|
|
|
if directory == "" {
|
|
|
|
panic("empty directory")
|
|
|
|
}
|
2017-03-01 00:31:57 +03:00
|
|
|
return &DiskMutationLogger{directory: directory}
|
|
|
|
}
|
|
|
|
|
2017-03-02 21:50:29 +03:00
|
|
|
// filename returns the filename to write to. The oldest filename must come
|
|
|
|
// first in lexical order.
|
2017-03-01 00:31:57 +03:00
|
|
|
func (d *DiskMutationLogger) filename() string {
|
|
|
|
now := time.Now().UTC()
|
2017-03-28 02:03:06 +03:00
|
|
|
return filepath.Join(d.directory, fmt.Sprintf("maintner-%s.mutlog", now.Format("2006-01-02")))
|
2017-03-01 00:31:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Log will write m to disk. If a mutation file does not exist for the current
|
|
|
|
// day, it will be created.
|
|
|
|
func (d *DiskMutationLogger) Log(m *maintpb.Mutation) error {
|
|
|
|
data, err := proto.Marshal(m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-28 02:03:06 +03:00
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
2017-04-14 01:08:15 +03:00
|
|
|
return reclog.AppendRecordToFile(d.filename(), data)
|
2017-03-01 00:31:57 +03:00
|
|
|
}
|
2017-03-02 21:50:29 +03:00
|
|
|
|
2017-04-14 01:08:15 +03:00
|
|
|
func (d *DiskMutationLogger) ForeachFile(fn func(fullPath string, fi os.FileInfo) error) error {
|
2017-04-30 00:15:37 +03:00
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
2017-03-22 20:01:56 +03:00
|
|
|
if d.directory == "" {
|
|
|
|
panic("empty directory")
|
2017-03-02 21:50:29 +03:00
|
|
|
}
|
2017-04-14 01:08:15 +03:00
|
|
|
// Walk guarantees that files are walked in lexical order, which we depend on.
|
|
|
|
return filepath.Walk(d.directory, func(path string, fi os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if fi.IsDir() && path != filepath.Clean(d.directory) {
|
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(fi.Name(), "maintner-") {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if !strings.HasSuffix(fi.Name(), ".mutlog") {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fn(path, fi)
|
|
|
|
})
|
|
|
|
}
|
2017-03-28 02:03:06 +03:00
|
|
|
|
2017-04-27 01:52:07 +03:00
|
|
|
func (d *DiskMutationLogger) GetMutations(ctx context.Context) <-chan MutationStreamEvent {
|
2017-04-30 00:15:37 +03:00
|
|
|
d.mu.Lock()
|
|
|
|
wasDone := d.done
|
|
|
|
d.done = true
|
|
|
|
d.mu.Unlock()
|
|
|
|
|
|
|
|
if wasDone {
|
|
|
|
// TODO: support subsequent Update? for now we only
|
|
|
|
// support the initial loading. The network mutation
|
|
|
|
// source is the new implementation with Update
|
|
|
|
// support.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:52:07 +03:00
|
|
|
ch := make(chan MutationStreamEvent, 50) // buffered: overlap gunzip/unmarshal with loading
|
2017-04-30 00:15:37 +03:00
|
|
|
|
2017-04-14 01:08:15 +03:00
|
|
|
go func() {
|
|
|
|
err := d.ForeachFile(func(fullPath string, fi os.FileInfo) error {
|
|
|
|
return reclog.ForeachFileRecord(fullPath, func(off int64, hdr, rec []byte) error {
|
2017-03-02 21:50:29 +03:00
|
|
|
m := new(maintpb.Mutation)
|
2017-04-14 01:08:15 +03:00
|
|
|
if err := proto.Unmarshal(rec, m); err != nil {
|
2017-03-02 21:50:29 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
select {
|
2017-04-27 01:52:07 +03:00
|
|
|
case ch <- MutationStreamEvent{Mutation: m}:
|
2017-04-14 01:08:15 +03:00
|
|
|
return nil
|
2017-03-02 21:50:29 +03:00
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
2017-04-14 01:08:15 +03:00
|
|
|
})
|
2017-03-02 21:50:29 +03:00
|
|
|
})
|
2017-04-27 01:52:07 +03:00
|
|
|
final := MutationStreamEvent{Err: err}
|
|
|
|
if err == nil {
|
|
|
|
final.End = true
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case ch <- final:
|
|
|
|
case <-ctx.Done():
|
2017-03-02 21:50:29 +03:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
return ch
|
|
|
|
}
|