<commit_before>package storage

import (
	"expvar"
	"io"
	"log"

	"golang.org/x/net/context"
	"golang.org/x/net/trace"
)

// NewLogFS creates a new FS which logs all calls to FS.
func NewLogFS(fs FS, name string, l *log.Logger) FS {
	return &LogFS{
		fs:     fs,
		name:   name,
		logger: l,
	}
}

// LogFS is an FS implementation which logs all filesystem calls.
type LogFS struct {
	fs FS

	name   string
	logger *log.Logger
}

// Open implements FS.  All calls to Open are logged and errors are logged seperately.
func (l *LogFS) Open(ctx context.Context, path string) (*File, error) {
	l.logger.Printf("%v: open: %v", l.name, path)
	f, err := l.fs.Open(ctx, path)
	if err != nil {
		l.logger.Printf("%v: open error: %v: %v", l.name, path, err)
	}
	return f, err
}

// Create implements FS.  All calls to Create are logged and errors are logged seperately.
func (l *LogFS) Create(ctx context.Context, path string) (io.WriteCloser, error) {
	l.logger.Printf("%v: create: %v", l.name, path)
	wc, err := l.fs.Create(ctx, path)
	if err != nil {
		l.logger.Printf("%v: create error: %v: %v", l.name, path, err)
	}
	return wc, err
}

// Delete implements FS.  All calls to Delete are logged and errors are logged seperately.
func (l *LogFS) Delete(ctx context.Context, path string) error {
	l.logger.Printf("%v: delete: %v", l.name, path)
	err := l.fs.Delete(ctx, path)
	if err != nil {
		l.logger.Printf("%v: delete error: %v: %v", l.name, path, err)
	}
	return err
}

// Walk implements FS.  No logs are written at this time.
func (l *LogFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return l.fs.Walk(ctx, path, fn)
}

// NewTraceFS creates a new FS which wraps an FS and records calls using
// golang.org/x/net/trace.
func NewTraceFS(fs FS, name string) FS {
	return &TraceFS{
		fs:   fs,
		name: name,
	}
}

// TraceFS is a FS implementation which wraps an FS and records
// calls using golang.org/x/net/trace.
type TraceFS struct {
	fs FS

	name string
}

// Open implements FS.  All calls to Open are logged via golang.org/x/net/trace.
func (t *TraceFS) Open(ctx context.Context, path string) (f *File, err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: open: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Open(ctx, path)
}

// Create implements FS.  All calls to Create are logged via golang.org/x/net/trace.
func (t *TraceFS) Create(ctx context.Context, path string) (wc io.WriteCloser, err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: create: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Create(ctx, path)
}

// Delete implements FS.  All calls to Delete are logged via golang.org/x/net/trace.
func (t *TraceFS) Delete(ctx context.Context, path string) (err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: delete: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Delete(ctx, path)
}

// Walk implements FS.  Nothing is traced at this time.
func (t *TraceFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return t.fs.Walk(ctx, path, fn)
}

// NewErrCountFS creates an FS which records stats based on usage.
func NewErrCountFS(fs FS, name string, err error) FS {
	status := expvar.NewMap(name)
	status.Set("open.total", new(expvar.Int))
	status.Set("open.count", new(expvar.Int))

	status.Set("create.total", new(expvar.Int))
	status.Set("create.count", new(expvar.Int))

	status.Set("delete.total", new(expvar.Int))
	status.Set("delete.count", new(expvar.Int))

	return &ErrCountFS{
		fs:     fs,
		status: status,
		err:    err,
	}
}

// ErrCountFS is an FS which records error counts for an FS.
type ErrCountFS struct {
	fs FS

	err    error
	status *expvar.Map
}

// Open implements FS.  All errors from Open are counted.
func (s ErrCountFS) Open(ctx context.Context, path string) (*File, error) {
	f, err := s.fs.Open(ctx, path)
	if err == s.err {
		s.status.Add("open.count", 1)
	}
	s.status.Add("open.total", 1)
	return f, err
}

// Create implements FS.  All errors from Create are counted.
func (s ErrCountFS) Create(ctx context.Context, path string) (io.WriteCloser, error) {
	wc, err := s.fs.Create(ctx, path)
	if err == s.err {
		s.status.Add("create.count", 1)
	}
	s.status.Add("create.total", 1)
	return wc, err
}

// Delete implements FS.  All errors from Delete are counted.
func (s ErrCountFS) Delete(ctx context.Context, path string) error {
	err := s.fs.Delete(ctx, path)
	if err == s.err {
		s.status.Add("delete.count", 1)
	}
	s.status.Add("delete.total", 1)
	return err
}

// Walk implements FS.  No stats are recorded at this time.
func (s ErrCountFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return s.fs.Walk(ctx, path, fn)
}
<commit_msg>return proper types instead of interfaces<commit_after>package storage

import (
	"expvar"
	"io"
	"log"

	"golang.org/x/net/context"
	"golang.org/x/net/trace"
)

// NewLogFS creates a new FS which logs all calls to FS.
func NewLogFS(fs FS, name string, l *log.Logger) *LogFS {
	return &LogFS{
		fs:     fs,
		name:   name,
		logger: l,
	}
}

// LogFS is an FS implementation which logs all filesystem calls.
type LogFS struct {
	fs FS

	name   string
	logger *log.Logger
}

// Open implements FS.  All calls to Open are logged and errors are logged seperately.
func (l *LogFS) Open(ctx context.Context, path string) (*File, error) {
	l.logger.Printf("%v: open: %v", l.name, path)
	f, err := l.fs.Open(ctx, path)
	if err != nil {
		l.logger.Printf("%v: open error: %v: %v", l.name, path, err)
	}
	return f, err
}

// Create implements FS.  All calls to Create are logged and errors are logged seperately.
func (l *LogFS) Create(ctx context.Context, path string) (io.WriteCloser, error) {
	l.logger.Printf("%v: create: %v", l.name, path)
	wc, err := l.fs.Create(ctx, path)
	if err != nil {
		l.logger.Printf("%v: create error: %v: %v", l.name, path, err)
	}
	return wc, err
}

// Delete implements FS.  All calls to Delete are logged and errors are logged seperately.
func (l *LogFS) Delete(ctx context.Context, path string) error {
	l.logger.Printf("%v: delete: %v", l.name, path)
	err := l.fs.Delete(ctx, path)
	if err != nil {
		l.logger.Printf("%v: delete error: %v: %v", l.name, path, err)
	}
	return err
}

// Walk implements FS.  No logs are written at this time.
func (l *LogFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return l.fs.Walk(ctx, path, fn)
}

// NewTraceFS creates a new FS which wraps an FS and records calls using
// golang.org/x/net/trace.
func NewTraceFS(fs FS, name string) *TraceFS {
	return &TraceFS{
		fs:   fs,
		name: name,
	}
}

// TraceFS is a FS implementation which wraps an FS and records
// calls using golang.org/x/net/trace.
type TraceFS struct {
	fs FS

	name string
}

// Open implements FS.  All calls to Open are logged via golang.org/x/net/trace.
func (t *TraceFS) Open(ctx context.Context, path string) (f *File, err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: open: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Open(ctx, path)
}

// Create implements FS.  All calls to Create are logged via golang.org/x/net/trace.
func (t *TraceFS) Create(ctx context.Context, path string) (wc io.WriteCloser, err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: create: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Create(ctx, path)
}

// Delete implements FS.  All calls to Delete are logged via golang.org/x/net/trace.
func (t *TraceFS) Delete(ctx context.Context, path string) (err error) {
	if tr, ok := trace.FromContext(ctx); ok {
		tr.LazyPrintf("%v: delete: %v", t.name, path)
		defer func() {
			if err != nil {
				tr.LazyPrintf("%v: error: %v", t.name, err)
				tr.SetError()
			}
		}()
	}
	return t.fs.Delete(ctx, path)
}

// Walk implements FS.  Nothing is traced at this time.
func (t *TraceFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return t.fs.Walk(ctx, path, fn)
}

// NewErrCountFS creates an FS which records stats based on usage.
func NewErrCountFS(fs FS, name string, err error) *ErrCountFS {
	status := expvar.NewMap(name)
	status.Set("open.total", new(expvar.Int))
	status.Set("open.count", new(expvar.Int))

	status.Set("create.total", new(expvar.Int))
	status.Set("create.count", new(expvar.Int))

	status.Set("delete.total", new(expvar.Int))
	status.Set("delete.count", new(expvar.Int))

	return &ErrCountFS{
		fs:     fs,
		status: status,
		err:    err,
	}
}

// ErrCountFS is an FS which records error counts for an FS.
type ErrCountFS struct {
	fs FS

	err    error
	status *expvar.Map
}

// Open implements FS.  All errors from Open are counted.
func (s ErrCountFS) Open(ctx context.Context, path string) (*File, error) {
	f, err := s.fs.Open(ctx, path)
	if err == s.err {
		s.status.Add("open.count", 1)
	}
	s.status.Add("open.total", 1)
	return f, err
}

// Create implements FS.  All errors from Create are counted.
func (s ErrCountFS) Create(ctx context.Context, path string) (io.WriteCloser, error) {
	wc, err := s.fs.Create(ctx, path)
	if err == s.err {
		s.status.Add("create.count", 1)
	}
	s.status.Add("create.total", 1)
	return wc, err
}

// Delete implements FS.  All errors from Delete are counted.
func (s ErrCountFS) Delete(ctx context.Context, path string) error {
	err := s.fs.Delete(ctx, path)
	if err == s.err {
		s.status.Add("delete.count", 1)
	}
	s.status.Add("delete.total", 1)
	return err
}

// Walk implements FS.  No stats are recorded at this time.
func (s ErrCountFS) Walk(ctx context.Context, path string, fn WalkFn) error {
	return s.fs.Walk(ctx, path, fn)
}
<|endoftext|>


<commit_before>package gorivets

import (
	"container/list"
	"strconv"
	"time"
)

type (
	// Least Recently Used container interface. Defines basic methods for
	// different implementations (see sized and time-based below).
	//
	// Method calls should be guarded by synchronization primitives in
	// multi-thread environment, unless it's explicitly said "friendly"
	LRU interface {
		// Add an element to the LRU.
		Add(k, v interface{}, size int64)
		// returns the element and marks it as recently used
		Get(k interface{}) (interface{}, bool)
		// returns the element, but not marking it as used recently
		Peek(k interface{}) (interface{}, bool)
		Delete(k interface{}) interface{}
		DeleteWithCallback(k interface{}, callback bool) interface{}
		// Garbage collection call, can be needed for time restricted LRU
		Sweep()
		Clear()
		// Multithread: friendly
		Len() int
		// Multithread: friendly
		Size() int64
	}

	Lru struct {
		list     *list.List
		elements map[interface{}]*list.Element
		size     int64
		maxSize  int64
		callback LruCallback
	}

	element struct {
		key  interface{}
		val  interface{}
		size int64
	}

	lru_ttl struct {
		list     *list.List
		elements map[interface{}]*list.Element
		size     int64
		maxSize  int64
		duration time.Duration
		callback LruCallback
	}

	element_ttl struct {
		key       interface{}
		val       interface{}
		size      int64
		expiredOn time.Time
	}

	LruCallback func(k, v interface{})
)

func NewLRU(maxSize int64, callback LruCallback) LRU {
	if maxSize < 1 {
		panic("LRU size=" + strconv.FormatInt(maxSize, 10) + " should be positive.")
	}
	l := new(Lru)
	l.list = list.New()
	l.elements = make(map[interface{}]*list.Element)
	l.size = 0
	l.maxSize = maxSize
	l.callback = callback
	return l
}

func NewTtlLRU(maxSize int64, duration time.Duration, callback LruCallback) LRU {
	if maxSize < 1 {
		panic("LRU size=" + strconv.FormatInt(maxSize, 10) + " should be positive.")
	}
	l := new(lru_ttl)
	l.list = list.New()
	l.elements = make(map[interface{}]*list.Element)
	l.size = 0
	l.maxSize = maxSize
	l.callback = callback
	l.duration = duration
	return l
}

// =============================== Lru =======================================
func (lru *Lru) Add(k, v interface{}, size int64) {
	lru.Delete(k)
	e := &element{key: k, val: v, size: size}
	el := lru.list.PushBack(e)
	lru.elements[k] = el
	lru.size += size
	for lru.size > lru.maxSize && lru.deleteLast() {
		// left empty intentionally
	}
}

func (lru *Lru) Get(k interface{}) (interface{}, bool) {
	if e, ok := lru.elements[k]; ok {
		lru.list.MoveToBack(e)
		return e.Value.(*element).val, true
	}
	return nil, false
}

func (lru *Lru) Peek(k interface{}) (interface{}, bool) {
	e, ok := lru.elements[k]
	return e, ok
}

func (lru *Lru) Delete(k interface{}) interface{} {
	return lru.DeleteWithCallback(k, true)
}

func (lru *Lru) DeleteWithCallback(k interface{}, callback bool) interface{} {
	el, ok := lru.elements[k]
	if !ok {
		return nil
	}
	delete(lru.elements, k)
	e := lru.list.Remove(el).(*element)
	lru.size -= e.size
	if callback && lru.callback != nil {
		lru.callback(e.key, e.val)
	}
	return e.val
}

func (lru *Lru) Sweep() {
}

// Clear the cache. This method will not invoke callbacks for the deleted
// elements
func (lru *Lru) Clear() {
	lru.list.Init()
	lru.elements = make(map[interface{}]*list.Element)
	lru.size = 0
}

func (lru *Lru) Len() int {
	return len(lru.elements)
}

func (lru *Lru) Size() int64 {
	return lru.size
}

func (lru *Lru) deleteLast() bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element)
	lru.Delete(e.key)
	return true
}

// ============================= lru_ttl =====================================
func (lru *lru_ttl) Add(k, v interface{}, size int64) {
	lru.Delete(k)
	now := time.Now()
	e := &element_ttl{key: k, val: v, size: size, expiredOn: now.Add(lru.duration)}
	el := lru.list.PushBack(e)
	lru.elements[k] = el
	lru.size += size
	for (lru.size > lru.maxSize || lru.lastExpired(now)) && lru.deleteLast() {
		// left empty intentionally
	}
}

func (lru *lru_ttl) Get(k interface{}) (interface{}, bool) {
	if e, ok := lru.elements[k]; ok {
		lru.list.MoveToBack(e)
		return e.Value.(*element_ttl).val, true
	}
	return nil, false
}

func (lru *lru_ttl) Peek(k interface{}) (interface{}, bool) {
	e, ok := lru.elements[k]
	return e, ok
}

func (lru *lru_ttl) Delete(k interface{}) interface{} {
	return lru.deleteWithCallback(k, true)
}

func (lru *lru_ttl) DeleteWithCallback(k interface{}, callback bool) interface{} {
	return lru.deleteWithCallback(k, callback)
}

func (lru *lru_ttl) delete(k interface{}) interface{} {
	return lru.deleteWithCallback(k, true)
}

func (lru *lru_ttl) deleteWithCallback(k interface{}, callback bool) interface{} {
	el, ok := lru.elements[k]
	if !ok {
		return nil
	}
	delete(lru.elements, k)
	e := lru.list.Remove(el).(*element_ttl)
	lru.size -= e.size
	if callback && lru.callback != nil {
		lru.callback(e.key, e.val)
	}
	return e.val
}

func (lru *lru_ttl) Sweep() {
	lru.timeCleanup()
}

// Clear the cache. This method will not invoke callbacks for the deleted
// elements
func (lru *lru_ttl) Clear() {
	lru.list.Init()
	lru.elements = make(map[interface{}]*list.Element)
	lru.size = 0
}

func (lru *lru_ttl) Len() int {
	return len(lru.elements)
}

func (lru *lru_ttl) Size() int64 {
	return lru.size
}

func (lru *lru_ttl) deleteLast() bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element_ttl)
	lru.delete(e.key)
	return true
}

func (lru *lru_ttl) lastExpired(now time.Time) bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element_ttl)
	return now.After(e.expiredOn)
}

func (lru *lru_ttl) timeCleanup() {
	now := time.Now()
	for lru.lastExpired(now) && lru.deleteLast() {
		// left empty intentionally
	}
}
<commit_msg>fix LRU TTL behaviour<commit_after>package gorivets

import (
	"container/list"
	"strconv"
	"time"
)

type (
	// Least Recently Used container interface. Defines basic methods for
	// different implementations (see sized and time-based below).
	//
	// Method calls should be guarded by synchronization primitives in
	// multi-thread environment, unless it's explicitly said "friendly"
	LRU interface {
		// Add an element to the LRU.
		Add(k, v interface{}, size int64)
		// returns the element and marks it as recently used
		Get(k interface{}) (interface{}, bool)
		// returns the element, but not marking it as used recently
		Peek(k interface{}) (interface{}, bool)

		// Deletes an element and call for callback if it is not nil
		// equivavalent to DeleteWithCallback(k, true)
		Delete(k interface{}) interface{}

		// Deletes an elementa and call callback function if callback==true and
		// the callback function was specified for the container
		DeleteWithCallback(k interface{}, callback bool) interface{}

		// Garbage collection call, can be needed for time restricted LRU
		Sweep()
		Clear()
		// Multithread: friendly
		Len() int
		// Multithread: friendly
		Size() int64
	}

	Lru struct {
		list     *list.List
		elements map[interface{}]*list.Element
		size     int64
		maxSize  int64
		callback LruCallback
	}

	element struct {
		key  interface{}
		val  interface{}
		size int64
	}

	lru_ttl struct {
		list     *list.List
		elements map[interface{}]*list.Element
		size     int64
		maxSize  int64
		duration time.Duration
		callback LruCallback
	}

	element_ttl struct {
		key       interface{}
		val       interface{}
		size      int64
		expiredOn time.Time
	}

	LruCallback func(k, v interface{})
)

func NewLRU(maxSize int64, callback LruCallback) LRU {
	if maxSize < 1 {
		panic("LRU size=" + strconv.FormatInt(maxSize, 10) + " should be positive.")
	}
	l := new(Lru)
	l.list = list.New()
	l.elements = make(map[interface{}]*list.Element)
	l.size = 0
	l.maxSize = maxSize
	l.callback = callback
	return l
}

func NewTtlLRU(maxSize int64, duration time.Duration, callback LruCallback) LRU {
	if maxSize < 1 {
		panic("LRU size=" + strconv.FormatInt(maxSize, 10) + " should be positive.")
	}
	l := new(lru_ttl)
	l.list = list.New()
	l.elements = make(map[interface{}]*list.Element)
	l.size = 0
	l.maxSize = maxSize
	l.callback = callback
	l.duration = duration
	return l
}

// =============================== Lru =======================================
func (lru *Lru) Add(k, v interface{}, size int64) {
	lru.Delete(k)
	e := &element{key: k, val: v, size: size}
	el := lru.list.PushBack(e)
	lru.elements[k] = el
	lru.size += size
	for lru.size > lru.maxSize && lru.deleteLast() {
		// left empty intentionally
	}
}

func (lru *Lru) Get(k interface{}) (interface{}, bool) {
	if e, ok := lru.elements[k]; ok {
		lru.list.MoveToBack(e)
		return e.Value.(*element).val, true
	}
	return nil, false
}

func (lru *Lru) Peek(k interface{}) (interface{}, bool) {
	e, ok := lru.elements[k]
	return e, ok
}

func (lru *Lru) Delete(k interface{}) interface{} {
	return lru.DeleteWithCallback(k, true)
}

func (lru *Lru) DeleteWithCallback(k interface{}, callback bool) interface{} {
	el, ok := lru.elements[k]
	if !ok {
		return nil
	}
	delete(lru.elements, k)
	e := lru.list.Remove(el).(*element)
	lru.size -= e.size
	if callback && lru.callback != nil {
		lru.callback(e.key, e.val)
	}
	return e.val
}

func (lru *Lru) Sweep() {
}

// Clear the cache. This method will not invoke callbacks for the deleted
// elements
func (lru *Lru) Clear() {
	lru.list.Init()
	lru.elements = make(map[interface{}]*list.Element)
	lru.size = 0
}

func (lru *Lru) Len() int {
	return len(lru.elements)
}

func (lru *Lru) Size() int64 {
	return lru.size
}

func (lru *Lru) deleteLast() bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element)
	lru.Delete(e.key)
	return true
}

// ============================= lru_ttl =====================================
func (lru *lru_ttl) Add(k, v interface{}, size int64) {
	lru.Delete(k)
	now := time.Now()
	e := &element_ttl{key: k, val: v, size: size, expiredOn: now.Add(lru.duration)}
	el := lru.list.PushBack(e)
	lru.elements[k] = el
	lru.size += size
	for (lru.size > lru.maxSize || lru.lastExpired(now)) && lru.deleteLast() {
		// left empty intentionally
	}
}

func (lru *lru_ttl) Get(k interface{}) (interface{}, bool) {
	if e, ok := lru.elements[k]; ok {
		et := e.Value.(*element_ttl)
		et.expiredOn = time.Now().Add(lru.duration)
		lru.list.MoveToBack(e)
		return et.val, true
	}
	return nil, false
}

func (lru *lru_ttl) Peek(k interface{}) (interface{}, bool) {
	e, ok := lru.elements[k]
	return e, ok
}

func (lru *lru_ttl) Delete(k interface{}) interface{} {
	return lru.deleteWithCallback(k, true)
}

func (lru *lru_ttl) DeleteWithCallback(k interface{}, callback bool) interface{} {
	return lru.deleteWithCallback(k, callback)
}

func (lru *lru_ttl) delete(k interface{}) interface{} {
	return lru.deleteWithCallback(k, true)
}

func (lru *lru_ttl) deleteWithCallback(k interface{}, callback bool) interface{} {
	el, ok := lru.elements[k]
	if !ok {
		return nil
	}
	delete(lru.elements, k)
	e := lru.list.Remove(el).(*element_ttl)
	lru.size -= e.size
	if callback && lru.callback != nil {
		lru.callback(e.key, e.val)
	}
	return e.val
}

func (lru *lru_ttl) Sweep() {
	lru.timeCleanup()
}

// Clear the cache. This method will not invoke callbacks for the deleted
// elements
func (lru *lru_ttl) Clear() {
	lru.list.Init()
	lru.elements = make(map[interface{}]*list.Element)
	lru.size = 0
}

func (lru *lru_ttl) Len() int {
	return len(lru.elements)
}

func (lru *lru_ttl) Size() int64 {
	return lru.size
}

func (lru *lru_ttl) deleteLast() bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element_ttl)
	lru.delete(e.key)
	return true
}

func (lru *lru_ttl) lastExpired(now time.Time) bool {
	el := lru.list.Front()
	if el == nil {
		return false
	}
	e := el.Value.(*element_ttl)
	return now.After(e.expiredOn)
}

func (lru *lru_ttl) timeCleanup() {
	now := time.Now()
	for lru.lastExpired(now) && lru.deleteLast() {
		// left empty intentionally
	}
}
<|endoftext|>


<commit_before>// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package lru

import (
	"container/list"
	"errors"
	"sync"
)

// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
	size      int
	evictList *list.List
	items     map[interface{}]*list.Element
	lock      sync.RWMutex
	onEvicted func(key interface{}, value interface{})
}

// entry is used to hold a value in the evictList
type entry struct {
	key   interface{}
	value interface{}
}

// New creates an LRU of the given size
func New(size int) (*Cache, error) {
	return NewWithEvict(size, nil)
}

func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
	if size <= 0 {
		return nil, errors.New("Must provide a positive size")
	}
	c := &Cache{
		size:      size,
		evictList: list.New(),
		items:     make(map[interface{}]*list.Element, size),
		onEvicted: onEvicted,
	}
	return c, nil
}

// Purge is used to completely clear the cache
func (c *Cache) Purge() {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.onEvicted != nil {
		for k, v := range c.items {
			c.onEvicted(k, v.Value.(*entry).value)
		}
	}

	c.evictList = list.New()
	c.items = make(map[interface{}]*list.Element, c.size)
}

// Add adds a value to the cache.  Returns true if an eviction occured.
func (c *Cache) Add(key, value interface{}) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	// Check for existing item
	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		ent.Value.(*entry).value = value
		return false
	}

	// Add new item
	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	evict := c.evictList.Len() > c.size
	// Verify size not exceeded
	if evict {
		c.removeOldest()
	}
	return evict
}

// Get looks up a key's value from the cache.
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		return ent.Value.(*entry).value, true
	}
	return
}

// Check if a key is in the cache, without updating the recent-ness or deleting it for being stale.
func (c *Cache) Contains(key interface{}) (ok bool) {
	c.lock.RLock()
	defer c.lock.RUnlock()

	_, ok = c.items[key]
	return ok
}

// Returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
// (If you find yourself using this a lot, you might be using the wrong sort of data structure, but there are some use cases where it's handy.)
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
	c.lock.RLock()
	defer c.lock.RUnlock()

	if ent, ok := c.items[key]; ok {
		return ent.Value.(*entry).value, true
	}
	return nil, ok
}

// ContainsOrAdd checks if a key is in the cache (without updating the recent-ness or deleting it for being stale),
// if not, adds the value. Returns whether found and whether an eviction occurred.
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	_, ok = c.items[key]
	if ok {
		return true, false
	}

	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	evict = c.evictList.Len() > c.size
	// Verify size not exceeded
	if evict {
		c.removeOldest()
	}
	return false, evict
}

// Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.removeElement(ent)
	}
}

// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.removeOldest()
}

// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *Cache) Keys() []interface{} {
	c.lock.RLock()
	defer c.lock.RUnlock()

	keys := make([]interface{}, len(c.items))
	ent := c.evictList.Back()
	i := 0
	for ent != nil {
		keys[i] = ent.Value.(*entry).key
		ent = ent.Prev()
		i++
	}

	return keys
}

// Len returns the number of items in the cache.
func (c *Cache) Len() int {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.evictList.Len()
}

// removeOldest removes the oldest item from the cache.
func (c *Cache) removeOldest() {
	ent := c.evictList.Back()
	if ent != nil {
		c.removeElement(ent)
	}
}

// removeElement is used to remove a given list element from the cache
func (c *Cache) removeElement(e *list.Element) {
	c.evictList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.items, kv.key)
	if c.onEvicted != nil {
		c.onEvicted(kv.key, kv.value)
	}
}
<commit_msg>Fixed potential memory leak from calling Purge very often<commit_after>// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package lru

import (
	"container/list"
	"errors"
	"sync"
)

// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
	size      int
	evictList *list.List
	items     map[interface{}]*list.Element
	lock      sync.RWMutex
	onEvicted func(key interface{}, value interface{})
}

// entry is used to hold a value in the evictList
type entry struct {
	key   interface{}
	value interface{}
}

// New creates an LRU of the given size
func New(size int) (*Cache, error) {
	return NewWithEvict(size, nil)
}

func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
	if size <= 0 {
		return nil, errors.New("Must provide a positive size")
	}
	c := &Cache{
		size:      size,
		evictList: list.New(),
		items:     make(map[interface{}]*list.Element, size),
		onEvicted: onEvicted,
	}
	return c, nil
}

// Purge is used to completely clear the cache
func (c *Cache) Purge() {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.onEvicted != nil {
		for k, v := range c.items {
			c.onEvicted(k, v.Value.(*entry).value)
		}
	}

	c.evictList.Init()

	for k := range c.items {
		delete(c.items, k)
	}
}

// Add adds a value to the cache.  Returns true if an eviction occured.
func (c *Cache) Add(key, value interface{}) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	// Check for existing item
	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		ent.Value.(*entry).value = value
		return false
	}

	// Add new item
	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	evict := c.evictList.Len() > c.size
	// Verify size not exceeded
	if evict {
		c.removeOldest()
	}
	return evict
}

// Get looks up a key's value from the cache.
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		return ent.Value.(*entry).value, true
	}
	return
}

// Check if a key is in the cache, without updating the recent-ness or deleting it for being stale.
func (c *Cache) Contains(key interface{}) (ok bool) {
	c.lock.RLock()
	defer c.lock.RUnlock()

	_, ok = c.items[key]
	return ok
}

// Returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
// (If you find yourself using this a lot, you might be using the wrong sort of data structure, but there are some use cases where it's handy.)
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
	c.lock.RLock()
	defer c.lock.RUnlock()

	if ent, ok := c.items[key]; ok {
		return ent.Value.(*entry).value, true
	}
	return nil, ok
}

// ContainsOrAdd checks if a key is in the cache (without updating the recent-ness or deleting it for being stale),
// if not, adds the value. Returns whether found and whether an eviction occurred.
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	_, ok = c.items[key]
	if ok {
		return true, false
	}

	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	evict = c.evictList.Len() > c.size
	// Verify size not exceeded
	if evict {
		c.removeOldest()
	}
	return false, evict
}

// Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.removeElement(ent)
	}
}

// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.removeOldest()
}

// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *Cache) Keys() []interface{} {
	c.lock.RLock()
	defer c.lock.RUnlock()

	keys := make([]interface{}, len(c.items))
	ent := c.evictList.Back()
	i := 0
	for ent != nil {
		keys[i] = ent.Value.(*entry).key
		ent = ent.Prev()
		i++
	}

	return keys
}

// Len returns the number of items in the cache.
func (c *Cache) Len() int {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.evictList.Len()
}

// removeOldest removes the oldest item from the cache.
func (c *Cache) removeOldest() {
	ent := c.evictList.Back()
	if ent != nil {
		c.removeElement(ent)
	}
}

// removeElement is used to remove a given list element from the cache
func (c *Cache) removeElement(e *list.Element) {
	c.evictList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.items, kv.key)
	if c.onEvicted != nil {
		c.onEvicted(kv.key, kv.value)
	}
}
<|endoftext|>


<commit_before>package main

import (
   "fmt"
   "io/ioutil"
   "encoding/json"
   "net"
   "os"
   "qlib"
   "sync"
   "time"
)

var sId chan int // recycles client ids back to main()
var sTimeout error = &tTimeoutError{}

func main() {
   aDb, err := NewUserDb("./userdb")
   if err != nil { panic(err) }
   aDb.Uid["u111111"] = &tUser{Nodes: map[string]int{"111111":1}}
   aDb.Uid["u222222"] = &tUser{Nodes: map[string]int{"222222":1}}
   aDb.Uid["u333333"] = &tUser{Nodes: map[string]int{"333333":1}}

   qlib.UDb = aDb
   qlib.Init("qstore")
   sId = make(chan int, 10)

   fmt.Printf("Starting Test Pass\n")
   sId <- 111111
   sId <- 222222
   for a := 0; true; a++ {
      aDawdle := a == 1
      qlib.NewLink(NewTc(<-sId, aDawdle))
   }
}

const ( _=iota; eRegister; eAddNode; eLogin; eListEdit; ePost; ePing; eAck )

type tTestClient struct {
   id, to int // who i am, who i send to
   count int // msg number
   deferLogin bool // test login timeout feature
   ack chan int // writer tells reader to issue ack to qlib
   closed bool // when about to shut down
   readDeadline time.Time // set by qlib
}

func NewTc(i int, iDawdle bool) *tTestClient {
   return &tTestClient{
      id: i, to: i+111111,
      deferLogin: iDawdle,
      ack: make(chan int,10),
   }
}

func (o *tTestClient) Read(buf []byte) (int, error) {
   if o.count % 10 == 9 {
      return 0, &net.OpError{Op:"log out"}
   }

   var aDlC <-chan time.Time
   if !o.readDeadline.IsZero() {
      aDl := time.NewTimer(o.readDeadline.Sub(time.Now()))
      defer aDl.Stop()
      aDlC = aDl.C
   }

   aUnit := 200 * time.Millisecond; if o.deferLogin { aUnit = 6 * time.Second }
   aTmr := time.NewTimer(aUnit)
   defer aTmr.Stop()

   var aHead map[string]interface{}
   var aData string

   select {
   case <-o.ack:
      aHead = tMsg{"Op":eAck, "Id":"n", "Type":"n"}
   case <-aTmr.C:
      o.count++
      if o.deferLogin {
         aHead = tMsg{}
      } else if o.count == 1 {
         aHead = tMsg{"Op":eLogin, "Uid":"u"+fmt.Sprint(o.id), "NodeId":fmt.Sprint(o.id)}
      } else {
         aHead = tMsg{"Op":ePost, "Id":"n", "For":[]string{"u"+fmt.Sprint(o.to)}}
         aData = fmt.Sprintf(" |msg %d|", o.count)
      }
   case <-aDlC:
      return 0, &net.OpError{Op:"timeout",Err:sTimeout}
   }

   aMsg := qlib.PackMsg(aHead, []byte(aData))
   fmt.Printf("%d testclient.read %s\n", o.id, string(aMsg))
   return copy(buf, aMsg), nil
}

func (o *tTestClient) Write(buf []byte) (int, error) {
   if o.closed {
      fmt.Printf("%d testclient.write was closed\n", o.id)
      return 0, &net.OpError{Op:"closed"}
   }

   aTmr := time.NewTimer(2 * time.Second)

   select {
   case o.ack <- 1:
      aTmr.Stop()
   case <-aTmr.C:
      fmt.Printf("%d testclient.write timed out on ack\n", o.id)
      return 0, &net.OpError{Op:"noack"}
   }

   fmt.Printf("%d testclient.write got %s\n", o.id, string(buf))
   return len(buf), nil
}

func (o *tTestClient) SetReadDeadline(i time.Time) error {
   o.readDeadline = i
   return nil
}

func (o *tTestClient) Close() error {
   o.closed = true;
   time.AfterFunc(10*time.Millisecond, func(){ sId <- o.id })
   return nil
}

func (o *tTestClient) LocalAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) RemoteAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) SetDeadline(time.Time) error { return nil }
func (o *tTestClient) SetWriteDeadline(time.Time) error { return nil }

type tTimeoutError struct{}
func (o *tTimeoutError) Error() string   { return "i/o timeout" }
func (o *tTimeoutError) Timeout() bool   { return true }
func (o *tTimeoutError) Temporary() bool { return true }

type tMsg map[string]interface{}

//: these are instructions/guidance comments
//: you'll implement the public api to add/edit userdb records
//: for all ops, you look up a record in cache,
//:   and if not there call getRecord and cache the result
//:   lookups are done with aObj := o.Uid[iUid] (or o.Alias, o.Group)
//: for add/edit ops, you then modify the cache object, then call putRecord
//: locking
//:   cache read ops are done inside o.xyzDoor.RLock/RUnlock()
//:   cache add/delete ops are done inside o.xyzDoor.Lock/Unlock()
//:   Uid and Group object updates are done inside aObj.door.Lock/Unlock()
//: records are stored as files in subdirectories of o.root: uid, alias, group
//:   uid/* & group/* files are json format
//:   alias/* files are symlinks to Uid

type tUserDb struct {
   root string // top-level directory
   temp string // temp subdirectory; write files here first

   // cache records here
   uidDoor sync.RWMutex
   Uid map[string]*tUser

   aliasDoor sync.RWMutex
   Alias map[string]string // value is Uid

   groupDoor sync.RWMutex
   Group map[string]*tGroup
}

type tUser struct {
   door sync.RWMutex
   Nodes map[string]int // value is NodeRef
   Aliases []tAlias // public names for the user
}

type tAlias struct {
   En string // in english
   Nat string // in whatever language
}

type tGroup struct {
   door sync.RWMutex
   Uid map[string]tMember
}

type tMember struct {
   Alias string // invited/joined by this Alias
   Joined bool // use a date here?
}

type tUserDbErr string
func (o tUserDbErr) Error() string { return string(o) }

type tType string
const (
   eTuid   tType = "uid"
   eTalias tType = "alias"
   eTgroup  tType = "group"
)

//: add a crash recovery pass on startup
//: examine temp dir
//:   complete any pending transactions
//: in transaction
//:   sync temp dir instead of data dir
//:   remove temp file in commitDir
//:   drop .tmp files

func NewUserDb(iPath string) (*tUserDb, error) {
   for _, a := range [...]tType{ "temp", eTuid, eTalias, eTgroup } {
      err := os.MkdirAll(iPath + "/" + string(a), 0700)
      if err != nil { return nil, err }
   }

   aDb := new(tUserDb)
   aDb.root = iPath+"/"
   aDb.temp = aDb.root + "temp"
   aDb.Uid = make(map[string]*tUser)
   aDb.Alias = make(map[string]string)
   aDb.Group = make(map[string]*tGroup)

   return aDb, nil
}

func (o *tUserDb) Test() error {
   //: exercise the api, print diagnostics
   //: invoke from main() before tTestClient loop; stop program if tests fail
   return nil
}

//: below is the public api

func (o *tUserDb) AddUser(iUid, iNewNode string, iAliases []string) (aAliases []string, err error) {
   //: add user if iUid not in db
   return []string{}, nil
}

func (o *tUserDb) SetAliases(iUid, iNode string, iAliases []string) (aAliases []string, err error) {
   //: replace aliases if iUid in db and has iNode, and iAliases elements are unique
   return []string{}, nil
}

func (o *tUserDb) AddNode(iUid, iNode, iNewNode string) (aNodeRef int, err error) {
   //: add iNewNode if iUid in db and has iNode
   return 0, nil
}

func (o *tUserDb) DropNode(iUid, iNode string) error {
   //: delete iNode if iUid in db and has iNode
   return nil
}

//func (o *tUserDb) DropUser(iUid string) error {
//   return nil
//}

func (o *tUserDb) Verify(iUid, iNode string) (aNodeRef int, err error) {
   //: return noderef if iUid in db and has iNode
   // trivial implementation for qlib testing
   if o.Uid[iUid] != nil && o.Uid[iUid].Nodes[iNode] != 0 {
      return 1, nil
   }
   return 0, tUserDbErr("no such user/node")
}

func (o *tUserDb) GetNodes(iUid string) (aNodes []string, err error) {
   //: return noderefs if iUid in db
   // trivial implementation for qlib testing
   for aN,_ := range o.Uid[iUid].Nodes {
      aNodes = append(aNodes, aN)
   }
   return aNodes, nil
}

func (o *tUserDb) Lookup(iAlias string) (aUid string, err error) {
   //: return uid if iAlias in db
   return "", nil
}

func (o *tUserDb) ListInvite(iList, iBy, iAlias string) error {
   //: if iAlias in db, and iBy in db & iList (or make iList and add iBy), list iAlias
   return nil
}

func (o *tUserDb) ListJoin(iList, iAlias, iUid string) (aAlias string, err error) {
   //: return listed alias if iList in db and iUid in iList, list iAlias if != ""
   return "", nil
}

func (o *tUserDb) ListDrop(iList, iBy, iUid string) error {
   //: remove iUid from iList if iBy in iList
   return nil
}

func (o *tUserDb) ListLookup(iList, iBy string) (aUids []string, err error) {
   //: return uids if iBy in list
   return []string{}, nil
}

// pull a file into a cache object
func (o *tUserDb) getRecord(iType tType, iId string) (interface{}, error) {
   var err error
   var aObj interface{}
   aPath := o.root + string(iType) + "/" + iId

   // in case putRecord was interrupted
   err = os.Link(aPath + ".tmp", aPath)
   if err != nil {
      if !os.IsExist(err) && !os.IsNotExist(err) { return nil, err }
   } else {
      fmt.Println("getRecord: finished transaction for "+aPath)
   }

   switch (iType) {
   default:
      panic("getRecord: unexpected type "+iType)
   case "alias":
      aLn, err := os.Readlink(aPath)
      if err != nil {
         if os.IsNotExist(err) { return nil, nil }
         return nil, err
      }
      return &aLn, nil
   case "uid":  aObj = &tUser{}
   case "group": aObj = &tGroup{}
   }

   aBuf, err := ioutil.ReadFile(aPath)
   if err != nil {
      if os.IsNotExist(err) { return nil, nil }
      return nil, err
   }

   err = json.Unmarshal(aBuf, aObj)
   return aObj, err
}

// save cache object to disk. getRecord must be called before this
func (o *tUserDb) putRecord(iType tType, iId string, iObj interface{}) error {
   var err error
   aPath := o.root + string(iType) + "/" + iId
   aTemp := o.temp + string(iType) + "_" + iId

   err = os.Remove(aPath + ".tmp")
   if err == nil {
      fmt.Println("putRecord: removed residual .tmp file for "+aPath)
   }

   switch (iType) {
   default:
      panic("putRecord: unexpected type "+iType)
   case "alias":
      err = os.Symlink(iObj.(string), aPath + ".tmp")
      if err != nil { return err }
      return o.commitDir(iType, aPath)
   case "uid", "group":
   }

   aBuf, err := json.Marshal(iObj)
   if err != nil { return err }

   aFd, err := os.OpenFile(aTemp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   if err != nil { return err }
   defer aFd.Close()

   for aPos,aLen := 0,0; aPos < len(aBuf); aPos += aLen {
      aLen, err = aFd.Write(aBuf[aPos:])
      if err != nil { return err }
   }

   err = aFd.Sync()
   if err != nil { return err }

   err = os.Link(aTemp, aPath + ".tmp")
   if err != nil { return err }
   err = os.Remove(aTemp)
   if err != nil { return err }

   return o.commitDir(iType, aPath)
}

// sync the directory and set the filename
func (o *tUserDb) commitDir(iType tType, iPath string) error {
   aFd, err := os.Open(o.root + string(iType))
   if err != nil { return err }
   defer aFd.Close()
   err = aFd.Sync()
   if err != nil { return err }

   err = os.Remove(iPath)
   if err != nil && !os.IsNotExist(err) { return err }
   err = os.Rename(iPath + ".tmp", iPath)
   return err
}
<commit_msg>userdb: add .fetchUser() to show cache management steps<commit_after>package main

import (
   "fmt"
   "io/ioutil"
   "encoding/json"
   "net"
   "os"
   "qlib"
   "sync"
   "time"
)

var sId chan int // recycles client ids back to main()
var sTimeout error = &tTimeoutError{}

func main() {
   aDb, err := NewUserDb("./userdb")
   if err != nil { panic(err) }
   aDb.Uid["u111111"] = &tUser{Nodes: map[string]int{"111111":1}}
   aDb.Uid["u222222"] = &tUser{Nodes: map[string]int{"222222":1}}
   aDb.Uid["u333333"] = &tUser{Nodes: map[string]int{"333333":1}}

   qlib.UDb = aDb
   qlib.Init("qstore")
   sId = make(chan int, 10)

   fmt.Printf("Starting Test Pass\n")
   sId <- 111111
   sId <- 222222
   for a := 0; true; a++ {
      aDawdle := a == 1
      qlib.NewLink(NewTc(<-sId, aDawdle))
   }
}

const ( _=iota; eRegister; eAddNode; eLogin; eListEdit; ePost; ePing; eAck )

type tTestClient struct {
   id, to int // who i am, who i send to
   count int // msg number
   deferLogin bool // test login timeout feature
   ack chan int // writer tells reader to issue ack to qlib
   closed bool // when about to shut down
   readDeadline time.Time // set by qlib
}

func NewTc(i int, iDawdle bool) *tTestClient {
   return &tTestClient{
      id: i, to: i+111111,
      deferLogin: iDawdle,
      ack: make(chan int,10),
   }
}

func (o *tTestClient) Read(buf []byte) (int, error) {
   if o.count % 10 == 9 {
      return 0, &net.OpError{Op:"log out"}
   }

   var aDlC <-chan time.Time
   if !o.readDeadline.IsZero() {
      aDl := time.NewTimer(o.readDeadline.Sub(time.Now()))
      defer aDl.Stop()
      aDlC = aDl.C
   }

   aUnit := 200 * time.Millisecond; if o.deferLogin { aUnit = 6 * time.Second }
   aTmr := time.NewTimer(aUnit)
   defer aTmr.Stop()

   var aHead map[string]interface{}
   var aData string

   select {
   case <-o.ack:
      aHead = tMsg{"Op":eAck, "Id":"n", "Type":"n"}
   case <-aTmr.C:
      o.count++
      if o.deferLogin {
         aHead = tMsg{}
      } else if o.count == 1 {
         aHead = tMsg{"Op":eLogin, "Uid":"u"+fmt.Sprint(o.id), "NodeId":fmt.Sprint(o.id)}
      } else {
         aHead = tMsg{"Op":ePost, "Id":"n", "For":[]string{"u"+fmt.Sprint(o.to)}}
         aData = fmt.Sprintf(" |msg %d|", o.count)
      }
   case <-aDlC:
      return 0, &net.OpError{Op:"timeout",Err:sTimeout}
   }

   aMsg := qlib.PackMsg(aHead, []byte(aData))
   fmt.Printf("%d testclient.read %s\n", o.id, string(aMsg))
   return copy(buf, aMsg), nil
}

func (o *tTestClient) Write(buf []byte) (int, error) {
   if o.closed {
      fmt.Printf("%d testclient.write was closed\n", o.id)
      return 0, &net.OpError{Op:"closed"}
   }

   aTmr := time.NewTimer(2 * time.Second)

   select {
   case o.ack <- 1:
      aTmr.Stop()
   case <-aTmr.C:
      fmt.Printf("%d testclient.write timed out on ack\n", o.id)
      return 0, &net.OpError{Op:"noack"}
   }

   fmt.Printf("%d testclient.write got %s\n", o.id, string(buf))
   return len(buf), nil
}

func (o *tTestClient) SetReadDeadline(i time.Time) error {
   o.readDeadline = i
   return nil
}

func (o *tTestClient) Close() error {
   o.closed = true;
   time.AfterFunc(10*time.Millisecond, func(){ sId <- o.id })
   return nil
}

func (o *tTestClient) LocalAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) RemoteAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) SetDeadline(time.Time) error { return nil }
func (o *tTestClient) SetWriteDeadline(time.Time) error { return nil }

type tTimeoutError struct{}
func (o *tTimeoutError) Error() string   { return "i/o timeout" }
func (o *tTimeoutError) Timeout() bool   { return true }
func (o *tTimeoutError) Temporary() bool { return true }

type tMsg map[string]interface{}

//: these are instructions/guidance comments
//: you'll implement the public api to add/edit userdb records
//: for all ops, you look up a record in cache,
//:   and if not there call getRecord and cache the result
//:   lookups are done with aObj := o.Uid[iUid] (or o.Alias, o.Group)
//: for add/edit ops, you then modify the cache object, then call putRecord
//: locking
//:   cache read ops are done inside o.xyzDoor.RLock/RUnlock()
//:   cache add/delete ops are done inside o.xyzDoor.Lock/Unlock()
//:   Uid and Group object updates are done inside aObj.door.Lock/Unlock()
//: records are stored as files in subdirectories of o.root: uid, alias, group
//:   uid/* & group/* files are json format
//:   alias/* files are symlinks to Uid

type tUserDb struct {
   root string // top-level directory
   temp string // temp subdirectory; write files here first

   // cache records here
   uidDoor sync.RWMutex
   Uid map[string]*tUser

   aliasDoor sync.RWMutex
   Alias map[string]string // value is Uid

   groupDoor sync.RWMutex
   Group map[string]*tGroup
}

type tUser struct {
   door sync.RWMutex
   Nodes map[string]int // value is NodeRef
   Aliases []tAlias // public names for the user
}

type tAlias struct {
   En string // in english
   Nat string // in whatever language
}

type tGroup struct {
   door sync.RWMutex
   Uid map[string]tMember
}

type tMember struct {
   Alias string // invited/joined by this Alias
   Joined bool // use a date here?
}

type tUserDbErr string
func (o tUserDbErr) Error() string { return string(o) }

type tType string
const (
   eTuid   tType = "uid"
   eTalias tType = "alias"
   eTgroup  tType = "group"
)

//: add a crash recovery pass on startup
//: examine temp dir
//:   complete any pending transactions
//: in transaction
//:   sync temp dir instead of data dir
//:   remove temp file in commitDir
//:   drop .tmp files

func NewUserDb(iPath string) (*tUserDb, error) {
   for _, a := range [...]tType{ "temp", eTuid, eTalias, eTgroup } {
      err := os.MkdirAll(iPath + "/" + string(a), 0700)
      if err != nil { return nil, err }
   }

   aDb := new(tUserDb)
   aDb.root = iPath+"/"
   aDb.temp = aDb.root + "temp"
   aDb.Uid = make(map[string]*tUser)
   aDb.Alias = make(map[string]string)
   aDb.Group = make(map[string]*tGroup)

   return aDb, nil
}

func (o *tUserDb) Test() error {
   //: exercise the api, print diagnostics
   //: invoke from main() before tTestClient loop; stop program if tests fail
   return nil
}

//: below is the public api

func (o *tUserDb) AddUser(iUid, iNewNode string, iAliases []string) (aAliases []string, err error) {
   //: add user if iUid not in db
   return []string{}, nil
}

func (o *tUserDb) SetAliases(iUid, iNode string, iAliases []string) (aAliases []string, err error) {
   //: replace aliases if iUid in db and has iNode, and iAliases elements are unique
   return []string{}, nil
}

func (o *tUserDb) AddNode(iUid, iNode, iNewNode string) (aNodeRef int, err error) {
   //: add iNewNode if iUid in db and has iNode
   return 0, nil
}

func (o *tUserDb) DropNode(iUid, iNode string) error {
   //: delete iNode if iUid in db and has iNode
   return nil
}

//func (o *tUserDb) DropUser(iUid string) error {
//   return nil
//}

func (o *tUserDb) Verify(iUid, iNode string) (aNodeRef int, err error) {
   //: return noderef if iUid in db and has iNode
   // trivial implementation for qlib testing
   if o.Uid[iUid] != nil && o.Uid[iUid].Nodes[iNode] != 0 {
      return 1, nil
   }
   return 0, tUserDbErr("no such user/node")
}

func (o *tUserDb) GetNodes(iUid string) (aNodes []string, err error) {
   //: return noderefs if iUid in db
   // trivial implementation for qlib testing
   for aN,_ := range o.Uid[iUid].Nodes {
      aNodes = append(aNodes, aN)
   }
   return aNodes, nil
}

func (o *tUserDb) Lookup(iAlias string) (aUid string, err error) {
   //: return uid if iAlias in db
   return "", nil
}

func (o *tUserDb) ListInvite(iList, iBy, iAlias string) error {
   //: if iAlias in db, and iBy in db & iList (or make iList and add iBy), list iAlias
   return nil
}

func (o *tUserDb) ListJoin(iList, iAlias, iUid string) (aAlias string, err error) {
   //: return listed alias if iList in db and iUid in iList, list iAlias if != ""
   return "", nil
}

func (o *tUserDb) ListDrop(iList, iBy, iUid string) error {
   //: remove iUid from iList if iBy in iList
   return nil
}

func (o *tUserDb) ListLookup(iList, iBy string) (aUids []string, err error) {
   //: return uids if iBy in list
   return []string{}, nil
}

func (o *tUserDb) fetchUser(iUid string) *tUser {
   o.uidDoor.RLock() // read-lock uid map
   aUser := o.Uid[iUid] // lookup user in map
   o.uidDoor.RUnlock()

   if aUser == nil { // user not in cache
      aObj, err := o.getRecord(eTuid, iUid) // lookup user on disk
      if err != nil { panic(err) }
      aUser = aObj.(*tUser) // "type assertion" to extract *tUser value from interface{}
      aUser.door.Lock() // write-lock user

      o.uidDoor.Lock() // write-lock uid map
      o.Uid[iUid] = aUser // add user to map
      o.uidDoor.Unlock()
   } else {
      aUser.door.Lock() // write-lock user
   }
   return aUser // user is write-locked and cached
   // must do putRecord() and .door.Unlock() on return value after changes!
}

// pull a file into a cache object
func (o *tUserDb) getRecord(iType tType, iId string) (interface{}, error) {
   var err error
   var aObj interface{}
   aPath := o.root + string(iType) + "/" + iId

   // in case putRecord was interrupted
   err = os.Link(aPath + ".tmp", aPath)
   if err != nil {
      if !os.IsExist(err) && !os.IsNotExist(err) { return nil, err }
   } else {
      fmt.Println("getRecord: finished transaction for "+aPath)
   }

   switch (iType) {
   default:
      panic("getRecord: unexpected type "+iType)
   case "alias":
      aLn, err := os.Readlink(aPath)
      if err != nil {
         if os.IsNotExist(err) { return nil, nil }
         return nil, err
      }
      return &aLn, nil
   case "uid":  aObj = &tUser{}
   case "group": aObj = &tGroup{}
   }

   aBuf, err := ioutil.ReadFile(aPath)
   if err != nil {
      if os.IsNotExist(err) { return aObj, nil }
      return nil, err
   }

   err = json.Unmarshal(aBuf, aObj)
   return aObj, err
}

// save cache object to disk. getRecord must be called before this
func (o *tUserDb) putRecord(iType tType, iId string, iObj interface{}) error {
   var err error
   aPath := o.root + string(iType) + "/" + iId
   aTemp := o.temp + string(iType) + "_" + iId

   err = os.Remove(aPath + ".tmp")
   if err == nil {
      fmt.Println("putRecord: removed residual .tmp file for "+aPath)
   }

   switch (iType) {
   default:
      panic("putRecord: unexpected type "+iType)
   case "alias":
      err = os.Symlink(iObj.(string), aPath + ".tmp")
      if err != nil { return err }
      return o.commitDir(iType, aPath)
   case "uid", "group":
   }

   aBuf, err := json.Marshal(iObj)
   if err != nil { return err }

   aFd, err := os.OpenFile(aTemp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   if err != nil { return err }
   defer aFd.Close()

   for aPos,aLen := 0,0; aPos < len(aBuf); aPos += aLen {
      aLen, err = aFd.Write(aBuf[aPos:])
      if err != nil { return err }
   }

   err = aFd.Sync()
   if err != nil { return err }

   err = os.Link(aTemp, aPath + ".tmp")
   if err != nil { return err }
   err = os.Remove(aTemp)
   if err != nil { return err }

   return o.commitDir(iType, aPath)
}

// sync the directory and set the filename
func (o *tUserDb) commitDir(iType tType, iPath string) error {
   aFd, err := os.Open(o.root + string(iType))
   if err != nil { return err }
   defer aFd.Close()
   err = aFd.Sync()
   if err != nil { return err }

   err = os.Remove(iPath)
   if err != nil && !os.IsNotExist(err) { return err }
   err = os.Rename(iPath + ".tmp", iPath)
   return err
}
<|endoftext|>


<commit_before>package main

import (
   "fmt"
   "io/ioutil"
   "encoding/json"
   "net"
   "os"
   "qlib"
   "sync"
   "time"
)

var sId chan int // recycles client ids back to main()
var sTimeout error = &tTimeoutError{}

func main() {
   aDb, err := NewUserDb("./userdb")
   if err != nil { panic(err) }
   aDb.Uid["u111111"] = &tUser{Nodes: map[string]int{"111111":1}}
   aDb.Uid["u222222"] = &tUser{Nodes: map[string]int{"222222":1}}
   aDb.Uid["u333333"] = &tUser{Nodes: map[string]int{"333333":1}}

   qlib.UDb = aDb
   qlib.Init("qstore")
   sId = make(chan int, 10)

   fmt.Printf("Starting Test Pass\n")
   sId <- 111111
   sId <- 222222
   for a := 0; true; a++ {
      aDawdle := a == 1
      qlib.NewLink(NewTc(<-sId, aDawdle))
   }
}

const ( _=iota; eRegister; eAddNode; eLogin; eListEdit; ePost; ePing; eAck )

type tTestClient struct {
   id, to int // who i am, who i send to
   count int // msg number
   deferLogin bool // test login timeout feature
   ack chan int // writer tells reader to issue ack to qlib
   closed bool // when about to shut down
   readDeadline time.Time // set by qlib
}

func NewTc(i int, iDawdle bool) *tTestClient {
   return &tTestClient{
      id: i, to: i+111111,
      deferLogin: iDawdle,
      ack: make(chan int,10),
   }
}

func (o *tTestClient) Read(buf []byte) (int, error) {
   if o.count % 10 == 9 {
      return 0, &net.OpError{Op:"log out"}
   }

   var aDlC <-chan time.Time
   if !o.readDeadline.IsZero() {
      aDl := time.NewTimer(o.readDeadline.Sub(time.Now()))
      defer aDl.Stop()
      aDlC = aDl.C
   }

   aUnit := 200 * time.Millisecond; if o.deferLogin { aUnit = 6 * time.Second }
   aTmr := time.NewTimer(aUnit)
   defer aTmr.Stop()

   var aHead map[string]interface{}
   var aData string

   select {
   case <-o.ack:
      aHead = tMsg{"Op":eAck, "Id":"n", "Type":"n"}
   case <-aTmr.C:
      o.count++
      if o.deferLogin {
         aHead = tMsg{}
      } else if o.count == 1 {
         aHead = tMsg{"Op":eLogin, "Uid":"u"+fmt.Sprint(o.id), "NodeId":fmt.Sprint(o.id)}
      } else {
         aHead = tMsg{"Op":ePost, "Id":"n", "For":[]string{"u"+fmt.Sprint(o.to)}}
         aData = fmt.Sprintf(" |msg %d|", o.count)
      }
   case <-aDlC:
      return 0, &net.OpError{Op:"timeout",Err:sTimeout}
   }

   aMsg := qlib.PackMsg(aHead, []byte(aData))
   fmt.Printf("%d testclient.read %s\n", o.id, string(aMsg))
   return copy(buf, aMsg), nil
}

func (o *tTestClient) Write(buf []byte) (int, error) {
   if o.closed {
      fmt.Printf("%d testclient.write was closed\n", o.id)
      return 0, &net.OpError{Op:"closed"}
   }

   aTmr := time.NewTimer(2 * time.Second)

   select {
   case o.ack <- 1:
      aTmr.Stop()
   case <-aTmr.C:
      fmt.Printf("%d testclient.write timed out on ack\n", o.id)
      return 0, &net.OpError{Op:"noack"}
   }

   fmt.Printf("%d testclient.write got %s\n", o.id, string(buf))
   return len(buf), nil
}

func (o *tTestClient) SetReadDeadline(i time.Time) error {
   o.readDeadline = i
   return nil
}

func (o *tTestClient) Close() error {
   o.closed = true;
   time.AfterFunc(10*time.Millisecond, func(){ sId <- o.id })
   return nil
}

func (o *tTestClient) LocalAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) RemoteAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) SetDeadline(time.Time) error { return nil }
func (o *tTestClient) SetWriteDeadline(time.Time) error { return nil }

type tTimeoutError struct{}
func (o *tTimeoutError) Error() string   { return "i/o timeout" }
func (o *tTimeoutError) Timeout() bool   { return true }
func (o *tTimeoutError) Temporary() bool { return true }

type tMsg map[string]interface{}

//: these are instructions/guidance comments
//: you'll implement the public api to add/edit userdb records
//: for all ops, you look up a record in cache,
//:   and if not there call getRecord and cache the result
//:   lookups are done with aObj := o.Uid[iUid] (or o.Alias, o.Group)
//: for add/edit ops, you then modify the cache object, then call putRecord
//: locking
//:   cache read ops are done inside o.xyzDoor.RLock/RUnlock()
//:   cache add/delete ops are done inside o.xyzDoor.Lock/Unlock()
//:   Uid and Group object updates are done inside aObj.door.Lock/Unlock()
//: records are stored as files in subdirectories of o.root: uid, alias, group
//:   uid/* & group/* files are json format
//:   alias/* files are symlinks to Uid

type tUserDb struct {
   root string // top-level directory
   temp string // temp subdirectory; write files here first
   uidDoor, alsDoor, lstDoor sync.RWMutex // protect cache during update

   // cache records here
   Uid map[string]*tUser
   Alias map[string]string // value is Uid
   Group map[string]*tGroup
}

type tUser struct {
   door sync.RWMutex
   Nodes map[string]int // value is NodeRef
   Aliases []tAlias // public names for the user
}

type tAlias struct {
   En string // in english
   Nat string // in whatever language
}

type tGroup struct {
   door sync.RWMutex
   Uid map[string]tMember
}

type tMember struct {
   Alias string // invited/joined by this Alias
   Joined bool // use a date here?
}

type tUserDbErr string
func (o tUserDbErr) Error() string { return string(o) }

type tType string
const (
   eTuid   tType = "uid"
   eTalias tType = "alias"
   eTgroup  tType = "group"
)

//: add a crash recovery pass on startup
//: examine temp dir
//:   complete any pending transactions
//: in transaction
//:   sync temp dir instead of data dir
//:   remove temp file in commitDir
//:   drop .tmp files

func NewUserDb(iPath string) (*tUserDb, error) {
   for _, a := range [...]tType{ "temp", eTuid, eTalias, eTgroup } {
      err := os.MkdirAll(iPath + "/" + string(a), 0700)
      if err != nil { return nil, err }
   }

   aDb := new(tUserDb)
   aDb.root = iPath+"/"
   aDb.temp = aDb.root + "temp"
   aDb.Uid = make(map[string]*tUser)
   aDb.Alias = make(map[string]string)
   aDb.Group = make(map[string]*tGroup)

   return aDb, nil
}

func (o *tUserDb) Test() error {
   //: exercise the api, print diagnostics
   //: invoke from main() before tTestClient loop; stop program if tests fail
   return nil
}

//: below is the public api

func (o *tUserDb) AddUser(iUid, iNewNode string, iAliases []string) (aAliases []string, err error) {
   //: add user if iUid not in db
   return []string{}, nil
}

func (o *tUserDb) SetAliases(iUid, iNode string, iAliases []string) (aAliases []string, err error) {
   //: replace aliases if iUid in db and has iNode, and iAliases elements are unique
   return []string{}, nil
}

func (o *tUserDb) AddNode(iUid, iNode, iNewNode string) (aNodeRef int, err error) {
   //: add iNewNode if iUid in db and has iNode
   return 0, nil
}

func (o *tUserDb) DropNode(iUid, iNode string) error {
   //: delete iNode if iUid in db and has iNode
   return nil
}

//func (o *tUserDb) DropUser(iUid string) error {
//   return nil
//}

func (o *tUserDb) Verify(iUid, iNode string) (aNodeRef int, err error) {
   //: return noderef if iUid in db and has iNode
   // trivial implementation for qlib testing
   if o.Uid[iUid] != nil && o.Uid[iUid].Nodes[iNode] != 0 {
      return 1, nil
   }
   return 0, tUserDbErr("no such user/node")
}

func (o *tUserDb) GetNodes(iUid string) (aNodes []string, err error) {
   //: return noderefs if iUid in db
   // trivial implementation for qlib testing
   for aN,_ := range o.Uid[iUid].Nodes {
      aNodes = append(aNodes, aN)
   }
   return aNodes, nil
}

func (o *tUserDb) Lookup(iAlias string) (aUid string, err error) {
   //: return uid if iAlias in db
   return "", nil
}

func (o *tUserDb) ListInvite(iList, iBy, iAlias string) error {
   //: if iAlias in db, and iBy in db & iList (or make iList and add iBy), list iAlias
   return nil
}

func (o *tUserDb) ListJoin(iList, iAlias, iUid string) (aAlias string, err error) {
   //: return listed alias if iList in db and iUid in iList, list iAlias if != ""
   return "", nil
}

func (o *tUserDb) ListDrop(iList, iBy, iUid string) error {
   //: remove iUid from iList if iBy in iList
   return nil
}

func (o *tUserDb) ListLookup(iList, iBy string) (aUids []string, err error) {
   //: return uids if iBy in list
   return []string{}, nil
}

// pull a file into a cache object
func (o *tUserDb) getRecord(iType tType, iId string) (interface{}, error) {
   var err error
   var aObj interface{}
   aPath := o.root + string(iType) + "/" + iId

   // in case putRecord was interrupted
   err = os.Link(aPath + ".tmp", aPath)
   if err != nil {
      if !os.IsExist(err) && !os.IsNotExist(err) { return nil, err }
   } else {
      fmt.Println("getRecord: finished transaction for "+aPath)
   }

   switch (iType) {
   default:
      panic("getRecord: unexpected type "+iType)
   case "alias":
      aLn, err := os.Readlink(aPath)
      if err != nil {
         if os.IsNotExist(err) { return nil, nil }
         return nil, err
      }
      return &aLn, nil
   case "uid":  aObj = &tUser{}
   case "group": aObj = &tGroup{}
   }

   aBuf, err := ioutil.ReadFile(aPath)
   if err != nil {
      if os.IsNotExist(err) { return nil, nil }
      return nil, err
   }

   err = json.Unmarshal(aBuf, aObj)
   return aObj, err
}

// save cache object to disk. getRecord must be called before this
func (o *tUserDb) putRecord(iType tType, iId string, iObj interface{}) error {
   var err error
   aPath := o.root + string(iType) + "/" + iId
   aTemp := o.temp + string(iType) + "_" + iId

   err = os.Remove(aPath + ".tmp")
   if err == nil {
      fmt.Println("putRecord: removed residual .tmp file for "+aPath)
   }

   switch (iType) {
   default:
      panic("putRecord: unexpected type "+iType)
   case "alias":
      err = os.Symlink(iObj.(string), aPath + ".tmp")
      if err != nil { return err }
      return o.commitDir(iType, aPath)
   case "uid", "group":
   }

   aBuf, err := json.Marshal(iObj)
   if err != nil { return err }

   aFd, err := os.OpenFile(aTemp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   if err != nil { return err }
   defer aFd.Close()

   for aPos,aLen := 0,0; aPos < len(aBuf); aPos += aLen {
      aLen, err = aFd.Write(aBuf[aPos:])
      if err != nil { return err }
   }

   err = aFd.Sync()
   if err != nil { return err }

   err = os.Link(aTemp, aPath + ".tmp")
   if err != nil { return err }
   err = os.Remove(aTemp)
   if err != nil { return err }

   return o.commitDir(iType, aPath)
}

// sync the directory and set the filename
func (o *tUserDb) commitDir(iType tType, iPath string) error {
   aFd, err := os.Open(o.root + string(iType))
   if err != nil { return err }
   defer aFd.Close()
   err = aFd.Sync()
   if err != nil { return err }

   err = os.Remove(iPath)
   if err != nil && !os.IsNotExist(err) { return err }
   err = os.Rename(iPath + ".tmp", iPath)
   return err
}
<commit_msg>userdb: clarify tUserDb RWlocks<commit_after>package main

import (
   "fmt"
   "io/ioutil"
   "encoding/json"
   "net"
   "os"
   "qlib"
   "sync"
   "time"
)

var sId chan int // recycles client ids back to main()
var sTimeout error = &tTimeoutError{}

func main() {
   aDb, err := NewUserDb("./userdb")
   if err != nil { panic(err) }
   aDb.Uid["u111111"] = &tUser{Nodes: map[string]int{"111111":1}}
   aDb.Uid["u222222"] = &tUser{Nodes: map[string]int{"222222":1}}
   aDb.Uid["u333333"] = &tUser{Nodes: map[string]int{"333333":1}}

   qlib.UDb = aDb
   qlib.Init("qstore")
   sId = make(chan int, 10)

   fmt.Printf("Starting Test Pass\n")
   sId <- 111111
   sId <- 222222
   for a := 0; true; a++ {
      aDawdle := a == 1
      qlib.NewLink(NewTc(<-sId, aDawdle))
   }
}

const ( _=iota; eRegister; eAddNode; eLogin; eListEdit; ePost; ePing; eAck )

type tTestClient struct {
   id, to int // who i am, who i send to
   count int // msg number
   deferLogin bool // test login timeout feature
   ack chan int // writer tells reader to issue ack to qlib
   closed bool // when about to shut down
   readDeadline time.Time // set by qlib
}

func NewTc(i int, iDawdle bool) *tTestClient {
   return &tTestClient{
      id: i, to: i+111111,
      deferLogin: iDawdle,
      ack: make(chan int,10),
   }
}

func (o *tTestClient) Read(buf []byte) (int, error) {
   if o.count % 10 == 9 {
      return 0, &net.OpError{Op:"log out"}
   }

   var aDlC <-chan time.Time
   if !o.readDeadline.IsZero() {
      aDl := time.NewTimer(o.readDeadline.Sub(time.Now()))
      defer aDl.Stop()
      aDlC = aDl.C
   }

   aUnit := 200 * time.Millisecond; if o.deferLogin { aUnit = 6 * time.Second }
   aTmr := time.NewTimer(aUnit)
   defer aTmr.Stop()

   var aHead map[string]interface{}
   var aData string

   select {
   case <-o.ack:
      aHead = tMsg{"Op":eAck, "Id":"n", "Type":"n"}
   case <-aTmr.C:
      o.count++
      if o.deferLogin {
         aHead = tMsg{}
      } else if o.count == 1 {
         aHead = tMsg{"Op":eLogin, "Uid":"u"+fmt.Sprint(o.id), "NodeId":fmt.Sprint(o.id)}
      } else {
         aHead = tMsg{"Op":ePost, "Id":"n", "For":[]string{"u"+fmt.Sprint(o.to)}}
         aData = fmt.Sprintf(" |msg %d|", o.count)
      }
   case <-aDlC:
      return 0, &net.OpError{Op:"timeout",Err:sTimeout}
   }

   aMsg := qlib.PackMsg(aHead, []byte(aData))
   fmt.Printf("%d testclient.read %s\n", o.id, string(aMsg))
   return copy(buf, aMsg), nil
}

func (o *tTestClient) Write(buf []byte) (int, error) {
   if o.closed {
      fmt.Printf("%d testclient.write was closed\n", o.id)
      return 0, &net.OpError{Op:"closed"}
   }

   aTmr := time.NewTimer(2 * time.Second)

   select {
   case o.ack <- 1:
      aTmr.Stop()
   case <-aTmr.C:
      fmt.Printf("%d testclient.write timed out on ack\n", o.id)
      return 0, &net.OpError{Op:"noack"}
   }

   fmt.Printf("%d testclient.write got %s\n", o.id, string(buf))
   return len(buf), nil
}

func (o *tTestClient) SetReadDeadline(i time.Time) error {
   o.readDeadline = i
   return nil
}

func (o *tTestClient) Close() error {
   o.closed = true;
   time.AfterFunc(10*time.Millisecond, func(){ sId <- o.id })
   return nil
}

func (o *tTestClient) LocalAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) RemoteAddr() net.Addr { return &net.UnixAddr{"e", "a"} }
func (o *tTestClient) SetDeadline(time.Time) error { return nil }
func (o *tTestClient) SetWriteDeadline(time.Time) error { return nil }

type tTimeoutError struct{}
func (o *tTimeoutError) Error() string   { return "i/o timeout" }
func (o *tTimeoutError) Timeout() bool   { return true }
func (o *tTimeoutError) Temporary() bool { return true }

type tMsg map[string]interface{}

//: these are instructions/guidance comments
//: you'll implement the public api to add/edit userdb records
//: for all ops, you look up a record in cache,
//:   and if not there call getRecord and cache the result
//:   lookups are done with aObj := o.Uid[iUid] (or o.Alias, o.Group)
//: for add/edit ops, you then modify the cache object, then call putRecord
//: locking
//:   cache read ops are done inside o.xyzDoor.RLock/RUnlock()
//:   cache add/delete ops are done inside o.xyzDoor.Lock/Unlock()
//:   Uid and Group object updates are done inside aObj.door.Lock/Unlock()
//: records are stored as files in subdirectories of o.root: uid, alias, group
//:   uid/* & group/* files are json format
//:   alias/* files are symlinks to Uid

type tUserDb struct {
   root string // top-level directory
   temp string // temp subdirectory; write files here first

   // cache records here
   uidDoor sync.RWMutex
   Uid map[string]*tUser

   aliasDoor sync.RWMutex
   Alias map[string]string // value is Uid

   groupDoor sync.RWMutex
   Group map[string]*tGroup
}

type tUser struct {
   door sync.RWMutex
   Nodes map[string]int // value is NodeRef
   Aliases []tAlias // public names for the user
}

type tAlias struct {
   En string // in english
   Nat string // in whatever language
}

type tGroup struct {
   door sync.RWMutex
   Uid map[string]tMember
}

type tMember struct {
   Alias string // invited/joined by this Alias
   Joined bool // use a date here?
}

type tUserDbErr string
func (o tUserDbErr) Error() string { return string(o) }

type tType string
const (
   eTuid   tType = "uid"
   eTalias tType = "alias"
   eTgroup  tType = "group"
)

//: add a crash recovery pass on startup
//: examine temp dir
//:   complete any pending transactions
//: in transaction
//:   sync temp dir instead of data dir
//:   remove temp file in commitDir
//:   drop .tmp files

func NewUserDb(iPath string) (*tUserDb, error) {
   for _, a := range [...]tType{ "temp", eTuid, eTalias, eTgroup } {
      err := os.MkdirAll(iPath + "/" + string(a), 0700)
      if err != nil { return nil, err }
   }

   aDb := new(tUserDb)
   aDb.root = iPath+"/"
   aDb.temp = aDb.root + "temp"
   aDb.Uid = make(map[string]*tUser)
   aDb.Alias = make(map[string]string)
   aDb.Group = make(map[string]*tGroup)

   return aDb, nil
}

func (o *tUserDb) Test() error {
   //: exercise the api, print diagnostics
   //: invoke from main() before tTestClient loop; stop program if tests fail
   return nil
}

//: below is the public api

func (o *tUserDb) AddUser(iUid, iNewNode string, iAliases []string) (aAliases []string, err error) {
   //: add user if iUid not in db
   return []string{}, nil
}

func (o *tUserDb) SetAliases(iUid, iNode string, iAliases []string) (aAliases []string, err error) {
   //: replace aliases if iUid in db and has iNode, and iAliases elements are unique
   return []string{}, nil
}

func (o *tUserDb) AddNode(iUid, iNode, iNewNode string) (aNodeRef int, err error) {
   //: add iNewNode if iUid in db and has iNode
   return 0, nil
}

func (o *tUserDb) DropNode(iUid, iNode string) error {
   //: delete iNode if iUid in db and has iNode
   return nil
}

//func (o *tUserDb) DropUser(iUid string) error {
//   return nil
//}

func (o *tUserDb) Verify(iUid, iNode string) (aNodeRef int, err error) {
   //: return noderef if iUid in db and has iNode
   // trivial implementation for qlib testing
   if o.Uid[iUid] != nil && o.Uid[iUid].Nodes[iNode] != 0 {
      return 1, nil
   }
   return 0, tUserDbErr("no such user/node")
}

func (o *tUserDb) GetNodes(iUid string) (aNodes []string, err error) {
   //: return noderefs if iUid in db
   // trivial implementation for qlib testing
   for aN,_ := range o.Uid[iUid].Nodes {
      aNodes = append(aNodes, aN)
   }
   return aNodes, nil
}

func (o *tUserDb) Lookup(iAlias string) (aUid string, err error) {
   //: return uid if iAlias in db
   return "", nil
}

func (o *tUserDb) ListInvite(iList, iBy, iAlias string) error {
   //: if iAlias in db, and iBy in db & iList (or make iList and add iBy), list iAlias
   return nil
}

func (o *tUserDb) ListJoin(iList, iAlias, iUid string) (aAlias string, err error) {
   //: return listed alias if iList in db and iUid in iList, list iAlias if != ""
   return "", nil
}

func (o *tUserDb) ListDrop(iList, iBy, iUid string) error {
   //: remove iUid from iList if iBy in iList
   return nil
}

func (o *tUserDb) ListLookup(iList, iBy string) (aUids []string, err error) {
   //: return uids if iBy in list
   return []string{}, nil
}

// pull a file into a cache object
func (o *tUserDb) getRecord(iType tType, iId string) (interface{}, error) {
   var err error
   var aObj interface{}
   aPath := o.root + string(iType) + "/" + iId

   // in case putRecord was interrupted
   err = os.Link(aPath + ".tmp", aPath)
   if err != nil {
      if !os.IsExist(err) && !os.IsNotExist(err) { return nil, err }
   } else {
      fmt.Println("getRecord: finished transaction for "+aPath)
   }

   switch (iType) {
   default:
      panic("getRecord: unexpected type "+iType)
   case "alias":
      aLn, err := os.Readlink(aPath)
      if err != nil {
         if os.IsNotExist(err) { return nil, nil }
         return nil, err
      }
      return &aLn, nil
   case "uid":  aObj = &tUser{}
   case "group": aObj = &tGroup{}
   }

   aBuf, err := ioutil.ReadFile(aPath)
   if err != nil {
      if os.IsNotExist(err) { return nil, nil }
      return nil, err
   }

   err = json.Unmarshal(aBuf, aObj)
   return aObj, err
}

// save cache object to disk. getRecord must be called before this
func (o *tUserDb) putRecord(iType tType, iId string, iObj interface{}) error {
   var err error
   aPath := o.root + string(iType) + "/" + iId
   aTemp := o.temp + string(iType) + "_" + iId

   err = os.Remove(aPath + ".tmp")
   if err == nil {
      fmt.Println("putRecord: removed residual .tmp file for "+aPath)
   }

   switch (iType) {
   default:
      panic("putRecord: unexpected type "+iType)
   case "alias":
      err = os.Symlink(iObj.(string), aPath + ".tmp")
      if err != nil { return err }
      return o.commitDir(iType, aPath)
   case "uid", "group":
   }

   aBuf, err := json.Marshal(iObj)
   if err != nil { return err }

   aFd, err := os.OpenFile(aTemp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   if err != nil { return err }
   defer aFd.Close()

   for aPos,aLen := 0,0; aPos < len(aBuf); aPos += aLen {
      aLen, err = aFd.Write(aBuf[aPos:])
      if err != nil { return err }
   }

   err = aFd.Sync()
   if err != nil { return err }

   err = os.Link(aTemp, aPath + ".tmp")
   if err != nil { return err }
   err = os.Remove(aTemp)
   if err != nil { return err }

   return o.commitDir(iType, aPath)
}

// sync the directory and set the filename
func (o *tUserDb) commitDir(iType tType, iPath string) error {
   aFd, err := os.Open(o.root + string(iType))
   if err != nil { return err }
   defer aFd.Close()
   err = aFd.Sync()
   if err != nil { return err }

   err = os.Remove(iPath)
   if err != nil && !os.IsNotExist(err) { return err }
   err = os.Rename(iPath + ".tmp", iPath)
   return err
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tag

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"strconv"
)

var atomTypes = map[int]string{
	0:  "uint8",
	1:  "text",
	13: "jpeg",
	14: "png",
	21: "uint8",
}

var atoms = atomNames(map[string]string{
	"\xa9alb": "album",
	"\xa9art": "artist",
	"\xa9ART": "artist",
	"aART":    "album_artist",
	"\xa9day": "year",
	"\xa9nam": "title",
	"\xa9gen": "genre",
	"trkn":    "track",
	"\xa9wrt": "composer",
	"\xa9too": "encoder",
	"cprt":    "copyright",
	"covr":    "picture",
	"\xa9grp": "grouping",
	"keyw":    "keyword",
	"\xa9lyr": "lyrics",
	"\xa9cmt": "comment",
	"tmpo":    "tempo",
	"cpil":    "compilation",
	"disk":    "disc",
})

type atomNames map[string]string

func (f atomNames) Name(n string) []string {
	res := make([]string, 1)
	for k, v := range f {
		if v == n {
			res = append(res, k)
		}
	}
	return res
}

// metadataMP4 is the implementation of Metadata for MP4 tag (atom) data.
type metadataMP4 map[string]interface{}

// ReadAtoms reads MP4 metadata atoms from the io.ReadSeeker into a Metadata, returning
// non-nil error if there was a problem.
func ReadAtoms(r io.ReadSeeker) (Metadata, error) {
	_, err := r.Seek(0, os.SEEK_SET)
	if err != nil {
		return nil, err
	}
	m := make(metadataMP4)
	err = m.readAtoms(r)
	return m, err
}

func (m metadataMP4) readAtoms(r io.ReadSeeker) error {
	for {
		var size uint32
		err := binary.Read(r, binary.BigEndian, &size)
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		name, err := readString(r, 4)
		if err != nil {
			return err
		}

		switch name {
		case "meta":
			// next_item_id (int32)
			_, err := readBytes(r, 4)
			if err != nil {
				return err
			}
			fallthrough
		case "moov", "udta", "ilst":
			return m.readAtoms(r)
		case "free":
			_, err := r.Seek(int64(size-8), os.SEEK_CUR)
			if err != nil {
				return err
			}
			continue
		case "mdat": // stop when we get to the data
			return nil
		}

		b, err := readBytes(r, int(size-8))
		if err != nil {
			return err
		}

		_, ok := atoms[name]
		if !ok {
			continue
		}

		// 16: name + size + "data" + size (4 bytes each), have already read 8
		b = b[8:]
		class := getInt(b[1:4])
		contentType, ok := atomTypes[class]
		if !ok {
			return fmt.Errorf("invalid content type: %v", class)
		}

		b = b[8:]
		switch name {
		case "trkn", "disk":
			m[name] = int(b[3])
			m[name+"_count"] = int(b[5])
		default:
			var data interface{}
			// 4: atom version (1 byte) + atom flags (3 bytes)
			// 4: NULL (usually locale indicator)
			switch contentType {
			case "text":
				data = string(b)

			case "uint8":
				data = getInt(b[:1])

			case "jpeg", "png":
				data = &Picture{
					Ext:      contentType,
					MIMEType: "image/" + contentType,
					Data:     b,
				}
			}
			m[name] = data
		}
	}
}

func (metadataMP4) Format() Format     { return MP4 }
func (metadataMP4) FileType() FileType { return AAC }

func (m metadataMP4) Raw() map[string]interface{} { return m }

func (m metadataMP4) getString(n []string) string {
	for _, k := range n {
		if x, ok := m[k]; ok {
			return x.(string)
		}
	}
	return ""
}

func (m metadataMP4) getInt(n []string) int {
	for _, k := range n {
		if x, ok := m[k]; ok {
			return x.(int)
		}
	}
	return 0
}

func (m metadataMP4) Title() string {
	return m.getString(atoms.Name("title"))
}

func (m metadataMP4) Artist() string {
	return m.getString(atoms.Name("artist"))
}

func (m metadataMP4) Album() string {
	return m.getString(atoms.Name("album"))
}

func (m metadataMP4) AlbumArtist() string {
	return m.getString(atoms.Name("album_artist"))
}

func (m metadataMP4) Composer() string {
	return m.getString(atoms.Name("composer"))
}

func (m metadataMP4) Genre() string {
	return m.getString(atoms.Name("genre"))
}

func (m metadataMP4) Year() int {
	date := m.getString(atoms.Name("year"))
	if len(date) >= 4 {
		year, _ := strconv.Atoi(date[:4])
		return year
	}
	return 0
}

func (m metadataMP4) Track() (int, int) {
	x := m.getInt([]string{"trkn"})
	if n, ok := m["trkn_count"]; ok {
		return x, n.(int)
	}
	return x, 0
}

func (m metadataMP4) Disc() (int, int) {
	x := m.getInt([]string{"disk"})
	if n, ok := m["disk_count"]; ok {
		return x, n.(int)
	}
	return x, 0
}

func (m metadataMP4) Lyrics() string {
	t, ok := m["\xa9lyr"]
	if !ok {
		return ""
	}
	return t.(string)
}

func (m metadataMP4) Picture() *Picture {
	v, ok := m["covr"]
	if !ok {
		return nil
	}
	return v.(*Picture)
}
<commit_msg>Add more tags to mp4<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tag

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"strconv"
)

var atomTypes = map[int]string{
	0:  "uint8",
	1:  "text",
	13: "jpeg",
	14: "png",
	21: "uint8",
}

var atoms = atomNames(map[string]string{
	"\xa9alb": "album",
	"\xa9art": "artist",
	"\xa9ART": "artist",
	"aART":    "album_artist",
	"\xa9day": "year",
	"\xa9nam": "title",
	"\xa9gen": "genre",
	"trkn":    "track",
	"\xa9wrt": "composer",
	"\xa9too": "encoder",
	"cprt":    "copyright",
	"covr":    "picture",
	"\xa9grp": "grouping",
	"keyw":    "keyword",
	"\xa9lyr": "lyrics",
	"\xa9cmt": "comment",
	"tmpo":    "tempo",
	"cpil":    "compilation",
	"disk":    "disc",
})

type atomNames map[string]string

func (f atomNames) Name(n string) []string {
	res := make([]string, 1)
	for k, v := range f {
		if v == n {
			res = append(res, k)
		}
	}
	return res
}

// metadataMP4 is the implementation of Metadata for MP4 tag (atom) data.
type metadataMP4 map[string]interface{}

// ReadAtoms reads MP4 metadata atoms from the io.ReadSeeker into a Metadata, returning
// non-nil error if there was a problem.
func ReadAtoms(r io.ReadSeeker) (Metadata, error) {
	_, err := r.Seek(0, os.SEEK_SET)
	if err != nil {
		return nil, err
	}
	m := make(metadataMP4)
	err = m.readAtoms(r)
	return m, err
}

func (m metadataMP4) readAtoms(r io.ReadSeeker) error {
	for {
		var size uint32
		var subname string
		err := binary.Read(r, binary.BigEndian, &size)
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		name, err := readString(r, 4)
		if err != nil {
			return err
		}

		switch name {
		case "meta":
			// next_item_id (int32)
			_, err := readBytes(r, 4)
			if err != nil {
				return err
			}
			fallthrough
		case "moov", "udta", "ilst":
			return m.readAtoms(r)
		case "free":
			_, err := r.Seek(int64(size-8), os.SEEK_CUR)
			if err != nil {
				return err
			}
			continue
		case "mdat": // skip the data, the metadata can be at the end
			_, err := r.Seek(int64(size-8), os.SEEK_CUR)
			if err != nil {
				return err
			}
			continue
		case "----":
			/* Generic atom.
			   Should have 3 sub atoms : mean, name and data.
			   We check that mean=="com.apple.iTunes" and we use the subname as
			   the name, and move to the data atom if anything goes wrong, we jump
			   at the end of the "----" atom.  */

			// let's read the mean atom
			var subsize uint32
			err := binary.Read(r, binary.BigEndian, &subsize)
			if err != nil {
				return err
			}
			sub, err := readString(r, 4)
			if err != nil {
				return err
			}

			if sub != "mean" {
				// Something's wrong. Remove 8 read bytes from the size counter
				// since "----" is not a known atom name, the whole data will
				// be skipped
				size -= 8
				break
			}

			mean, err := readBytes(r, int(subsize-8))
			if err != nil {
				return err
			}
			// Remove the size of the mean atom from the size counter
			size -= subsize

			if string(mean[4:]) != "com.apple.iTunes" {
				// Something's wrong, skip this atom
				break
			}

			// Let's read the name atom
			err = binary.Read(r, binary.BigEndian, &subsize)
			if err != nil {
				return err
			}
			sub, err = readString(r, 4)
			if err != nil {
				return err
			}

			if sub != "name" {
				// Something's wrong
				size -= 8
				break
			}

			b, err := readBytes(r, int(subsize-8))
			if err != nil {
				return err
			}
			/* Remove the size of the name atom from the size counter.
			We should now be at the start of the data subatom and size should
			be equal to the size of the data atom and its header */
			size -= subsize

			subname = string(b[4:])
		}

		b, err := readBytes(r, int(size-8))
		if err != nil {
			return err
		}

		// Allow all known atoms and the valid "----" atoms
		_, ok := atoms[name]
		switch {
		case name == "----" && subname == "":
			continue
		case name == "----":
			name = subname
		case !ok:
			continue
		}

		// 16: name + size + "data" + size (4 bytes each), have already read 8
		b = b[8:]
		class := getInt(b[1:4])
		contentType, ok := atomTypes[class]
		if !ok {
			return fmt.Errorf("invalid content type: %v", class)
		}

		b = b[8:]
		switch name {
		case "trkn", "disk":
			m[name] = int(b[3])
			m[name+"_count"] = int(b[5])
		default:
			var data interface{}
			// 4: atom version (1 byte) + atom flags (3 bytes)
			// 4: NULL (usually locale indicator)
			switch contentType {
			case "text":
				data = string(b)

			case "uint8":
				data = getInt(b[:1])

			case "jpeg", "png":
				data = &Picture{
					Ext:      contentType,
					MIMEType: "image/" + contentType,
					Data:     b,
				}
			}
			m[name] = data
		}
	}
}

func (metadataMP4) Format() Format     { return MP4 }
func (metadataMP4) FileType() FileType { return AAC }

func (m metadataMP4) Raw() map[string]interface{} { return m }

func (m metadataMP4) getString(n []string) string {
	for _, k := range n {
		if x, ok := m[k]; ok {
			return x.(string)
		}
	}
	return ""
}

func (m metadataMP4) getInt(n []string) int {
	for _, k := range n {
		if x, ok := m[k]; ok {
			return x.(int)
		}
	}
	return 0
}

func (m metadataMP4) Title() string {
	return m.getString(atoms.Name("title"))
}

func (m metadataMP4) Artist() string {
	return m.getString(atoms.Name("artist"))
}

func (m metadataMP4) Album() string {
	return m.getString(atoms.Name("album"))
}

func (m metadataMP4) AlbumArtist() string {
	return m.getString(atoms.Name("album_artist"))
}

func (m metadataMP4) Composer() string {
	return m.getString(atoms.Name("composer"))
}

func (m metadataMP4) Genre() string {
	return m.getString(atoms.Name("genre"))
}

func (m metadataMP4) Year() int {
	date := m.getString(atoms.Name("year"))
	if len(date) >= 4 {
		year, _ := strconv.Atoi(date[:4])
		return year
	}
	return 0
}

func (m metadataMP4) Track() (int, int) {
	x := m.getInt([]string{"trkn"})
	if n, ok := m["trkn_count"]; ok {
		return x, n.(int)
	}
	return x, 0
}

func (m metadataMP4) Disc() (int, int) {
	x := m.getInt([]string{"disk"})
	if n, ok := m["disk_count"]; ok {
		return x, n.(int)
	}
	return x, 0
}

func (m metadataMP4) Lyrics() string {
	t, ok := m["\xa9lyr"]
	if !ok {
		return ""
	}
	return t.(string)
}

func (m metadataMP4) Picture() *Picture {
	v, ok := m["covr"]
	if !ok {
		return nil
	}
	return v.(*Picture)
}
<|endoftext|>


<commit_before>package shared

import (
	"encoding/json"
	"fmt"
	"log"
)

/*
Message is a base type for only reading out the operation to define the message
type.
*/
type Message struct {
	Type MsgType
}

/*
UpdateMessage contains the relevant information for notifiying peers of updates.
*/
type UpdateMessage struct {
	Type      MsgType
	Operation Operation
	Object    ObjectInfo
}

/*
CreateUpdateMessage is a convenience method for building an instance of the message.
*/
func CreateUpdateMessage(op Operation, obj ObjectInfo) UpdateMessage {
	return UpdateMessage{
		Type:      MsgUpdate,
		Operation: op,
		Object:    obj}
}

/*
JSON representation of this message.
*/
func (um *UpdateMessage) JSON() string {
	data, err := json.Marshal(um)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (um *UpdateMessage) String() string {
	return "UpdateMessage{Type:" + um.Type.String() +
		",Operation:" + um.Operation.String() +
		",Object:" + um.Object.String() + "}"
}

/*
RequestMessage is used to trigger the sending of messages or files from other
peers.
*/
type RequestMessage struct {
	Type           MsgType
	ObjType        ObjectType
	Identification string
}

/*
CreateRequestMessage is a convenience method for building an instance of the message.
*/
func CreateRequestMessage(ot ObjectType, identification string) RequestMessage {
	return RequestMessage{
		Type:           MsgRequest,
		ObjType:        ot,
		Identification: identification}
}

/*
JSON representation of this message.
*/
func (rm *RequestMessage) JSON() string {
	data, err := json.Marshal(rm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (rm *RequestMessage) String() string {
	return "RequestMessage{Type:" + rm.Type.String() +
		",ObjType:" + rm.ObjType.String() +
		",Identification:" + rm.Identification + "}"
}

/*
NotifyMessage is used to notify another peer of special cases.
*/
type NotifyMessage struct {
	Type           MsgType
	Notify         NotifyType
	Identification string
}

/*
CreateNotifyMessage is a convenience method for building an instance of the message.
*/
func CreateNotifyMessage(notify NotifyType, identification string) NotifyMessage {
	return NotifyMessage{
		Type:           MsgNotify,
		Notify:         notify,
		Identification: identification}
}

/*
JSON representation of this message.
*/
func (nm *NotifyMessage) JSON() string {
	data, err := json.Marshal(nm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (nm *NotifyMessage) String() string {
	return "NotifyMessage{Type:" + nm.Type.String() +
		",Notify:" + nm.Notify.String() +
		",Identification:" + nm.Identification + "}"
}

/*
LockMessage is the message used to lock an encrypted peer.
*/
type LockMessage struct {
	Type   MsgType
	Action LockAction
}

/*
CreateLockMessage is a convenience method for building an instance of the message.
*/
func CreateLockMessage(action LockAction) LockMessage {
	return LockMessage{
		Type:   MsgLock,
		Action: action}
}

/*
JSON representation of this message.
*/
func (lm *LockMessage) JSON() string {
	data, err := json.Marshal(lm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (lm *LockMessage) String() string {
	return "LockMessage{Type:" + lm.Type.String() +
		",Action:" + lm.Action.String() + "}"
}

/*
PushMessage is the message used to notify an encrypted peer of an incomming file
transfer.
*/
type PushMessage struct {
	Type           MsgType
	Identification string
	ObjType        ObjectType
}

/*
CreatePushMessage is a convenience method for building an instance of the message.
*/
func CreatePushMessage(identification string, ot ObjectType) PushMessage {
	return PushMessage{
		Type:           MsgPush,
		Identification: identification,
		ObjType:        ot}
}

/*
JSON representation of this message.
*/
func (pm *PushMessage) JSON() string {
	data, err := json.Marshal(pm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (pm *PushMessage) String() string {
	return "PushMessage{Type:" + pm.Type.String() +
		",Identification:" + pm.Identification +
		",ObjType:" + pm.ObjType.String() + "}"
}

/*
AuthenticationMessage is the message used to authenticate trusted peers.
*/
type AuthenticationMessage struct {
	Type      MsgType
	Encrypted []byte
}

/*
CreateAuthenticationMessage is a convenience method for building an instance of the message.
*/
func CreateAuthenticationMessage(encrypted []byte) AuthenticationMessage {
	return AuthenticationMessage{
		Type:      MsgChallenge,
		Encrypted: encrypted}
}

/*
JSON representation of this message.
*/
func (am *AuthenticationMessage) JSON() string {
	data, err := json.Marshal(am)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (am *AuthenticationMessage) String() string {
	return "AuthenticationMessage{Type:" + am.Type.String() +
		",Encrypted:" + fmt.Sprintf("%+v", am.Encrypted) + "}"
}
<commit_msg>added ObjectType to NotifyMessage<commit_after>package shared

import (
	"encoding/json"
	"fmt"
	"log"
)

/*
Message is a base type for only reading out the operation to define the message
type.
*/
type Message struct {
	Type MsgType
}

/*
UpdateMessage contains the relevant information for notifiying peers of updates.
*/
type UpdateMessage struct {
	Type      MsgType
	Operation Operation
	Object    ObjectInfo
}

/*
CreateUpdateMessage is a convenience method for building an instance of the message.
*/
func CreateUpdateMessage(op Operation, obj ObjectInfo) UpdateMessage {
	return UpdateMessage{
		Type:      MsgUpdate,
		Operation: op,
		Object:    obj}
}

/*
JSON representation of this message.
*/
func (um *UpdateMessage) JSON() string {
	data, err := json.Marshal(um)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (um *UpdateMessage) String() string {
	return "UpdateMessage{Type:" + um.Type.String() +
		",Operation:" + um.Operation.String() +
		",Object:" + um.Object.String() + "}"
}

/*
RequestMessage is used to trigger the sending of messages or files from other
peers.
*/
type RequestMessage struct {
	Type           MsgType
	ObjType        ObjectType
	Identification string
}

/*
CreateRequestMessage is a convenience method for building an instance of the message.
*/
func CreateRequestMessage(ot ObjectType, identification string) RequestMessage {
	return RequestMessage{
		Type:           MsgRequest,
		ObjType:        ot,
		Identification: identification}
}

/*
JSON representation of this message.
*/
func (rm *RequestMessage) JSON() string {
	data, err := json.Marshal(rm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (rm *RequestMessage) String() string {
	return "RequestMessage{Type:" + rm.Type.String() +
		",ObjType:" + rm.ObjType.String() +
		",Identification:" + rm.Identification + "}"
}

/*
NotifyMessage is used to notify another peer of special cases.
*/
type NotifyMessage struct {
	Type           MsgType
	Notify         NotifyType
	Identification string
	ObjType        ObjectType
}

/*
CreateNotifyMessage is a convenience method for building an instance of the message.
*/
func CreateNotifyMessage(notify NotifyType, identification string, objType ObjectType) NotifyMessage {
	return NotifyMessage{
		Type:           MsgNotify,
		Notify:         notify,
		Identification: identification,
		ObjType:        objType}
}

/*
JSON representation of this message.
*/
func (nm *NotifyMessage) JSON() string {
	data, err := json.Marshal(nm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (nm *NotifyMessage) String() string {
	return "NotifyMessage{Type:" + nm.Type.String() +
		",Notify:" + nm.Notify.String() +
		",Identification:" + nm.Identification +
		",ObjectType:" + nm.ObjType.String() + "}"
}

/*
LockMessage is the message used to lock an encrypted peer.
*/
type LockMessage struct {
	Type   MsgType
	Action LockAction
}

/*
CreateLockMessage is a convenience method for building an instance of the message.
*/
func CreateLockMessage(action LockAction) LockMessage {
	return LockMessage{
		Type:   MsgLock,
		Action: action}
}

/*
JSON representation of this message.
*/
func (lm *LockMessage) JSON() string {
	data, err := json.Marshal(lm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (lm *LockMessage) String() string {
	return "LockMessage{Type:" + lm.Type.String() +
		",Action:" + lm.Action.String() + "}"
}

/*
PushMessage is the message used to notify an encrypted peer of an incomming file
transfer.
*/
type PushMessage struct {
	Type           MsgType
	Identification string
	ObjType        ObjectType
}

/*
CreatePushMessage is a convenience method for building an instance of the message.
*/
func CreatePushMessage(identification string, ot ObjectType) PushMessage {
	return PushMessage{
		Type:           MsgPush,
		Identification: identification,
		ObjType:        ot}
}

/*
JSON representation of this message.
*/
func (pm *PushMessage) JSON() string {
	data, err := json.Marshal(pm)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (pm *PushMessage) String() string {
	return "PushMessage{Type:" + pm.Type.String() +
		",Identification:" + pm.Identification +
		",ObjType:" + pm.ObjType.String() + "}"
}

/*
AuthenticationMessage is the message used to authenticate trusted peers.
*/
type AuthenticationMessage struct {
	Type      MsgType
	Encrypted []byte
}

/*
CreateAuthenticationMessage is a convenience method for building an instance of the message.
*/
func CreateAuthenticationMessage(encrypted []byte) AuthenticationMessage {
	return AuthenticationMessage{
		Type:      MsgChallenge,
		Encrypted: encrypted}
}

/*
JSON representation of this message.
*/
func (am *AuthenticationMessage) JSON() string {
	data, err := json.Marshal(am)
	if err != nil {
		log.Println("Msg: JSON error:", err)
	}
	return string(data)
}

func (am *AuthenticationMessage) String() string {
	return "AuthenticationMessage{Type:" + am.Type.String() +
		",Encrypted:" + fmt.Sprintf("%+v", am.Encrypted) + "}"
}
<|endoftext|>


<commit_before>package rwvfs

// ExplicitFetchOpener is implemented by FileSystems that support a
// mode of operation where Open returns a lazily loaded file. This is
// useful for VFS implementations that are backed by slow (e.g.,
// network) data sources, so you can explicit fetch byte ranges at a
// higher level than Go I/O buffering.
type ExplicitFetchOpener interface {
	// ExplicitFetch sets whether Open should return files that must
	// be explicit fetched (i.e., that implement the Fetcher
	// interface).
	ExplicitFetch(bool)
}

// Fetcher is implemented by files that require explicit fetching to
// buffer data from remote (network-like) underlying sources.
type Fetcher interface {
	// Fetch fetches the specified byte range (start inclusive, end
	// exclusive) from a remote (network-like) underlying source. Only
	// these bytes ranges are available to be read.
	Fetch(start, end int64) error
}
<commit_msg>better interface for fetchers<commit_after>package rwvfs

import "golang.org/x/tools/godoc/vfs"

// FetcherOpener is implemented by FileSystems that support a mode of
// operation where OpenFetcher returns a lazily loaded file. This is
// useful for VFS implementations that are backed by slow (e.g.,
// network) data sources, so you can explicit fetch byte ranges at a
// higher level than Go I/O buffering.
type FetcherOpener interface {
	OpenFetcher(name string) (vfs.ReadSeekCloser, error)
}

// Fetcher is implemented by files that require explicit fetching to
// buffer data from remote (network-like) underlying sources.
type Fetcher interface {
	// Fetch fetches the specified byte range (start inclusive, end
	// exclusive) from a remote (network-like) underlying source. Only
	// these bytes ranges are available to be read.
	Fetch(start, end int64) error
}
<|endoftext|>