package mem import ( "io" "restic" "sync" "restic/errors" "restic/debug" ) type entry struct { Type restic.FileType Name string } type memMap map[entry][]byte // make sure that MemoryBackend implements backend.Backend var _ restic.Backend = &MemoryBackend{} // MemoryBackend is a mock backend that uses a map for storing all data in // memory. This should only be used for tests. type MemoryBackend struct { data memMap m sync.Mutex } // New returns a new backend that saves all data in a map in memory. func New() *MemoryBackend { be := &MemoryBackend{ data: make(memMap), } debug.Log("MemoryBackend.New", "created new memory backend") return be } // Test returns whether a file exists. func (be *MemoryBackend) Test(t restic.FileType, name string) (bool, error) { be.m.Lock() defer be.m.Unlock() debug.Log("MemoryBackend.Test", "test %v %v", t, name) if _, ok := be.data[entry{t, name}]; ok { return true, nil } return false, nil } // Load reads data from the backend. func (be *MemoryBackend) Load(h restic.Handle, p []byte, off int64) (int, error) { if err := h.Valid(); err != nil { return 0, err } be.m.Lock() defer be.m.Unlock() if h.Type == restic.ConfigFile { h.Name = "" } debug.Log("MemoryBackend.Load", "get %v offset %v len %v", h, off, len(p)) if _, ok := be.data[entry{h.Type, h.Name}]; !ok { return 0, errors.New("no such data") } buf := be.data[entry{h.Type, h.Name}] switch { case off > int64(len(buf)): return 0, errors.New("offset beyond end of file") case off < -int64(len(buf)): off = 0 case off < 0: off = int64(len(buf)) + off } buf = buf[off:] n := copy(p, buf) if len(p) > len(buf) { return n, io.ErrUnexpectedEOF } return n, nil } // Save adds new Data to the backend. func (be *MemoryBackend) Save(h restic.Handle, p []byte) error { if err := h.Valid(); err != nil { return err } be.m.Lock() defer be.m.Unlock() if h.Type == restic.ConfigFile { h.Name = "" } if _, ok := be.data[entry{h.Type, h.Name}]; ok { return errors.New("file already exists") } debug.Log("MemoryBackend.Save", "save %v bytes at %v", len(p), h) buf := make([]byte, len(p)) copy(buf, p) be.data[entry{h.Type, h.Name}] = buf return nil } // Stat returns information about a file in the backend. func (be *MemoryBackend) Stat(h restic.Handle) (restic.FileInfo, error) { be.m.Lock() defer be.m.Unlock() if err := h.Valid(); err != nil { return restic.FileInfo{}, err } if h.Type == restic.ConfigFile { h.Name = "" } debug.Log("MemoryBackend.Stat", "stat %v", h) e, ok := be.data[entry{h.Type, h.Name}] if !ok { return restic.FileInfo{}, errors.New("no such data") } return restic.FileInfo{Size: int64(len(e))}, nil } // Remove deletes a file from the backend. func (be *MemoryBackend) Remove(t restic.FileType, name string) error { be.m.Lock() defer be.m.Unlock() debug.Log("MemoryBackend.Remove", "get %v %v", t, name) if _, ok := be.data[entry{t, name}]; !ok { return errors.New("no such data") } delete(be.data, entry{t, name}) return nil } // List returns a channel which yields entries from the backend. func (be *MemoryBackend) List(t restic.FileType, done <-chan struct{}) <-chan string { be.m.Lock() defer be.m.Unlock() ch := make(chan string) var ids []string for entry := range be.data { if entry.Type != t { continue } ids = append(ids, entry.Name) } debug.Log("MemoryBackend.List", "list %v: %v", t, ids) go func() { defer close(ch) for _, id := range ids { select { case ch <- id: case <-done: return } } }() return ch } // Location returns the location of the backend (RAM). func (be *MemoryBackend) Location() string { return "RAM" } // Delete removes all data in the backend. func (be *MemoryBackend) Delete() error { be.m.Lock() defer be.m.Unlock() be.data = make(memMap) return nil } // Close closes the backend. func (be *MemoryBackend) Close() error { return nil }