func (j *kvJournal) Join(target Journal, mapping Mapping) error {
	kvTarget, ok := target.(*kvJournal)
	if !ok {
		return errors.New("journal: unjoinable journals")
	}
	meta := mapping.Get(j.id)
	if meta == nil {
		meta = j.Meta()
	} else if meta.ID != j.id {
		err := fmt.Errorf("journal: already joined %s -> %s", j.id, meta.ID)
		return err
	}
	cur := j.b.Cursor()
	k, v := cur.First()
	var copied int
	for k != nil {
		if v == nil {
			continue
		}
		copied++
		if err := kvTarget.b.Put(k, v); err != nil {
			return err
		}
		k, v = cur.Next()
	}
	meta.JoinedAt = time.Now().UnixNano()
	meta.ID = target.ID()  
	mapping.Set(j.id, meta)
	return nil
}
Join appends the current journal to the target one, reassigning atomically the mapping.
package journal
import (
	"errors"
	"fmt"
	"io"
	"strings"
	"sync"
	"time"
	"github.com/boltdb/bolt"
	"github.com/cznic/b"
)
type Journal interface {
	ID() ID
	Get(k string) *FileMeta
	Exists(k string) bool
	Set(k string, m *FileMeta) error
	Delete(k string) error
	Diff(j Journal) (added FileMetaList, deleted FileMetaList)
	Range(start string, limit int, fn func(k string, v *FileMeta) error) (string, error)
	Join(target Journal, mapping Mapping) error
	List() FileMetaList
	Close() error
	Meta() *JournalMeta
}
type kvJournal struct {
	id ID
	b  *bolt.Bucket
	tx *bolt.Tx
}
type btreeJournal struct {
	id ID
	t      *b.Tree
	mux    *sync.Mutex
	closed bool
}
func (b *btreeJournal) Close() error {
	b.mux.Lock()
	defer b.mux.Unlock()
	b.t.Close()
	b.closed = true
	return nil
}
func (j *kvJournal) Close() error {
	return nil
}
func (b *btreeJournal) Get(k string) *FileMeta {
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return nil
	}
	v, ok := b.t.Get(k)
	b.mux.Unlock()
	if ok {
		return v.(*FileMeta)
	}
	return nil
}
func (b *btreeJournal) Exists(k string) bool {
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return false
	}
	_, ok := b.t.Get(k)
	b.mux.Unlock()
	return ok
}
func (b *btreeJournal) Set(k string, m *FileMeta) error {
	if m == nil {
		return errors.New("journal: nil entries not allowed")
	}
	if len(k) == 0 {
		return errors.New("journal: zero-length keys not allowed")
	}
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return closedErr
	}
	b.t.Set(k, m)
	b.mux.Unlock()
	return nil
}
func (b *btreeJournal) Delete(k string) error {
	if len(k) == 0 {
		return errors.New("journal: zero-length keys not allowed")
	}
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return closedErr
	}
	b.t.Delete(k)
	b.mux.Unlock()
	return nil
}
var closedErr = errors.New("journal: closed already")
func (b *btreeJournal) Range(start string, limit int, fn func(k string, v *FileMeta) error) (string, error) {
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return "", closedErr
	}
	iter, ok := b.t.Seek(start)
	b.mux.Unlock()
	if !ok {
		return "", nil
	}
	defer iter.Close()
	var processed int
	var lastK string
	for {
		b.mux.Lock()
		if b.closed {
			b.mux.Unlock()
			return "", closedErr
		}
		k, v, err := iter.Next()
		b.mux.Unlock()
		if err == nil {
			lastK = k.(string)
			if err := fn(k.(string), v.(*FileMeta)); err == ErrRangeStop {
				return lastK, nil
			} else if err != nil {
				return lastK, err
			}
		} else {
			return "", nil
		}
		processed++
		if limit > 0 && processed >= limit {
			break
		}
	}
	return lastK, nil
}
func (b *btreeJournal) Join(target Journal, mapping Mapping) error {
	return errors.New("journal: unjoinable journals")
}
func (b *btreeJournal) Meta() *JournalMeta {
	firstKey, _ := b.t.First()
	lastKey, _ := b.t.Last()
	return &JournalMeta{
		ID: b.id,
		FirstKey:   firstKey.(string),
		LastKey:    lastKey.(string),
		CountTotal: b.t.Len(),
	}
}
func (b *btreeJournal) ID() ID {
	return b.id
}
func (b *btreeJournal) List() FileMetaList {
	b.mux.Lock()
	if b.closed {
		b.mux.Unlock()
		return nil
	}
	iter, err := b.t.SeekFirst()
	b.mux.Unlock()
	if err == nil {
		defer iter.Close()
	}
	var list FileMetaList
	var v interface{}
	for err == nil {
		b.mux.Lock()
		_, v, err = iter.Next()
		b.mux.Unlock()
		if err == nil {
			list = append(list, v.(*FileMeta))
		}
	}
	return list
}
func (prev *btreeJournal) Diff(next Journal) (added FileMetaList, deleted FileMetaList) {
	switch next := next.(type) {
	case *btreeJournal:
		prev.mux.Lock()
		if prev.closed {
			prev.mux.Unlock()
			return nil, nil
		}
		prevIter, prevErr := prev.t.SeekFirst()
		prev.mux.Unlock()
		if prevErr == nil {
			defer prevIter.Close()
		}
		next.mux.Lock()
		if next.closed {
			next.mux.Unlock()
			return nil, nil
		}
		nextIter, nextErr := next.t.SeekFirst()
		next.mux.Unlock()
		if nextErr == nil {
			defer nextIter.Close()
		}
		switch {
		case prevErr == io.EOF && nextErr == io.EOF:
			return nil, nil
		case prevErr == io.EOF:
			return next.List(), nil
		case nextErr == io.EOF:
			return nil, prev.List()
		default:
			prev.mux.Lock()
			prevK, prevV, prevErr := prevIter.Next()
			prev.mux.Unlock()
			next.mux.Lock()
			nextK, nextV, nextErr := nextIter.Next()
			next.mux.Unlock()
			for {
				switch {
				case prevErr == io.EOF:
					if nextErr == io.EOF {
						return
					}
					added = append(added, nextV.(*FileMeta))
					next.mux.Lock()
					nextK, nextV, nextErr = nextIter.Next()
					next.mux.Unlock()
				case nextErr == io.EOF:
					if prevErr == io.EOF {
						return
					}
					deleted = append(deleted, prevV.(*FileMeta))
					prev.mux.Lock()
					prevK, prevV, prevErr = prevIter.Next()
					prev.mux.Unlock()
				default:
					prevCmp := strings.Compare(prevK.(string), nextK.(string))
					switch {
					case prevCmp < 0:  
						deleted = append(deleted, prevV.(*FileMeta))
						prev.mux.Lock()
						prevK, prevV, prevErr = prevIter.Next()
						prev.mux.Unlock()
					case prevCmp > 0:  
						added = append(added, nextV.(*FileMeta))
						next.mux.Lock()
						nextK, nextV, nextErr = nextIter.Next()
						next.mux.Unlock()
					default:
						prev.mux.Lock()
						prevK, prevV, prevErr = prevIter.Next()
						prev.mux.Unlock()
						next.mux.Lock()
						nextK, nextV, nextErr = nextIter.Next()
						next.mux.Unlock()
					}
				}
			}
		}
	case *kvJournal:
		prev.mux.Lock()
		if prev.closed {
			prev.mux.Unlock()
			return nil, nil
		}
		prevIter, prevErr := prev.t.SeekFirst()
		prev.mux.Unlock()
		if prevErr == nil {
			defer prevIter.Close()
		}
		nextIter := next.b.Cursor()
		switch {
		case prevErr == io.EOF && nextIter == nil:
			return nil, nil
		case prevErr == io.EOF:
			return next.List(), nil
		case nextIter == nil:
			return nil, prev.List()
		default:
			prev.mux.Lock()
			prevK, prevV, prevErr := prevIter.Next()
			prev.mux.Unlock()
			nextK, nextV := nextIter.Next()
			for {
				switch {
				case prevErr == io.EOF:
					if nextK == nil {
						return
					}
					if nextV != nil {
						meta := new(FileMeta)
						meta.UnmarshalMsg(nextV)
						added = append(added, meta)
					}
					nextK, nextV = nextIter.Next()
				case nextK == nil:
					if prevErr == io.EOF {
						return
					}
					deleted = append(deleted, prevV.(*FileMeta))
					prev.mux.Lock()
					prevK, prevV, prevErr = prevIter.Next()
					prev.mux.Unlock()
				default:
					prevCmp := strings.Compare(prevK.(string), string(nextK))
					switch {
					case prevCmp < 0:  
						deleted = append(deleted, prevV.(*FileMeta))
						prev.mux.Lock()
						prevK, prevV, prevErr = prevIter.Next()
						prev.mux.Unlock()
					case prevCmp > 0:  
						if nextV != nil {
							meta := new(FileMeta)
							meta.UnmarshalMsg(nextV)
							added = append(added, meta)
						}
						nextK, nextV = nextIter.Next()
					default:
						prev.mux.Lock()
						prevK, prevV, prevErr = prevIter.Next()
						prev.mux.Unlock()
						nextK, nextV = nextIter.Next()
					}
				}
			}
		}
	default:
		panic("indifferentiable types")
	}
}
func (prev *kvJournal) Diff(next Journal) (added FileMetaList, deleted FileMetaList) {
	switch next := next.(type) {
	case *kvJournal:
		prevIter := prev.b.Cursor()
		nextIter := next.b.Cursor()
		switch {
		case prevIter == nil && nextIter == nil:
			return nil, nil
		case prevIter == nil:
			return next.List(), nil
		case nextIter == nil:
			return nil, prev.List()
		default:
			prevK, prevV := prevIter.Next()
			nextK, nextV := nextIter.Next()
			for {
				switch {
				case prevK == nil:
					if nextK == nil {
						return
					}
					if nextV != nil {
						meta := new(FileMeta)
						meta.UnmarshalMsg(nextV)
						added = append(added, meta)
					}
					nextK, nextV = nextIter.Next()
				case nextK == nil:
					if prevK == nil {
						return
					}
					if prevV != nil {
						meta := new(FileMeta)
						meta.UnmarshalMsg(prevV)
						deleted = append(deleted, meta)
					}
					prevK, prevV = prevIter.Next()
				default:
					prevCmp := strings.Compare(string(prevK), string(nextK))
					switch {
					case prevCmp < 0:  
						if prevV != nil {
							meta := new(FileMeta)
							meta.UnmarshalMsg(prevV)
							deleted = append(deleted, meta)
						}
						prevK, prevV = prevIter.Next()
					case prevCmp > 0:  
						if nextV != nil {
							meta := new(FileMeta)
							meta.UnmarshalMsg(nextV)
							added = append(added, meta)
						}
						nextK, nextV = nextIter.Next()
					default:
						prevK, prevV = prevIter.Next()
						nextK, nextV = nextIter.Next()
					}
				}
			}
		}
	case *btreeJournal:
		next.mux.Lock()
		if next.closed {
			next.mux.Unlock()
			return nil, nil
		}
		nextIter, nextErr := next.t.SeekFirst()
		next.mux.Unlock()
		if nextErr == nil {
			defer nextIter.Close()
		}
		prevIter := prev.b.Cursor()
		switch {
		case nextErr == io.EOF && prevIter == nil:
			return nil, nil
		case nextErr == io.EOF:
			return prev.List(), nil
		case prevIter == nil:
			return nil, next.List()
		default:
			next.mux.Lock()
			nextK, nextV, nextErr := nextIter.Next()
			next.mux.Unlock()
			prevK, prevV := prevIter.Next()
			for {
				switch {
				case nextErr == io.EOF:
					if prevK == nil {
						return
					}
					if prevV != nil {
						meta := new(FileMeta)
						meta.UnmarshalMsg(prevV)
						added = append(added, meta)
					}
					prevK, prevV = prevIter.Next()
				case prevK == nil:
					if nextErr == io.EOF {
						return
					}
					deleted = append(deleted, nextV.(*FileMeta))
					next.mux.Lock()
					nextK, nextV, nextErr = nextIter.Next()
					next.mux.Unlock()
				default:
					nextCmp := strings.Compare(nextK.(string), string(prevK))
					switch {
					case nextCmp < 0:  
						deleted = append(deleted, nextV.(*FileMeta))
						next.mux.Lock()
						nextK, nextV, nextErr = nextIter.Next()
						next.mux.Unlock()
					case nextCmp > 0:  
						if prevV != nil {
							meta := new(FileMeta)
							meta.UnmarshalMsg(prevV)
							added = append(added, meta)
						}
						prevK, prevV = prevIter.Next()
					default:
						next.mux.Lock()
						nextK, nextV, nextErr = nextIter.Next()
						next.mux.Unlock()
						prevK, prevV = prevIter.Next()
					}
				}
			}
		}
	default:
		panic("journal: indifferentiable types")
	}
}