| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | package cache | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 	"github.com/pkg/errors" | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Backend wraps a restic.Backend and adds a cache. | 
					
						
							|  |  |  | type Backend struct { | 
					
						
							|  |  |  | 	restic.Backend | 
					
						
							| 
									
										
										
										
											2017-09-24 22:24:11 +02:00
										 |  |  | 	*Cache | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// inProgress contains the handle for all files that are currently | 
					
						
							|  |  |  | 	// downloaded. The channel in the value is closed as soon as the download | 
					
						
							|  |  |  | 	// is finished. | 
					
						
							|  |  |  | 	inProgressMutex sync.Mutex | 
					
						
							|  |  |  | 	inProgress      map[restic.Handle]chan struct{} | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ensure cachedBackend implements restic.Backend | 
					
						
							|  |  |  | var _ restic.Backend = &Backend{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | func newBackend(be restic.Backend, c *Cache) *Backend { | 
					
						
							|  |  |  | 	return &Backend{ | 
					
						
							|  |  |  | 		Backend:    be, | 
					
						
							|  |  |  | 		Cache:      c, | 
					
						
							|  |  |  | 		inProgress: make(map[restic.Handle]chan struct{}), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | // Remove deletes a file from the backend and the cache if it has been cached. | 
					
						
							|  |  |  | func (b *Backend) Remove(ctx context.Context, h restic.Handle) error { | 
					
						
							|  |  |  | 	debug.Log("cache Remove(%v)", h) | 
					
						
							|  |  |  | 	err := b.Backend.Remove(ctx, h) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return b.Cache.Remove(h) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var autoCacheTypes = map[restic.FileType]struct{}{ | 
					
						
							|  |  |  | 	restic.IndexFile:    struct{}{}, | 
					
						
							|  |  |  | 	restic.SnapshotFile: struct{}{}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | // Save stores a new file in the backend and the cache. | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | func (b *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) { | 
					
						
							|  |  |  | 	if _, ok := autoCacheTypes[h.Type]; !ok { | 
					
						
							|  |  |  | 		return b.Backend.Save(ctx, h, rd) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:24:01 +02:00
										 |  |  | 	debug.Log("Save(%v): auto-store in the cache", h) | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	seeker, ok := rd.(io.Seeker) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("reader is not a seeker") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pos, err := seeker.Seek(0, io.SeekCurrent) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 		return errors.Wrapf(err, "Seek") | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 	if pos != 0 { | 
					
						
							|  |  |  | 		return errors.Errorf("reader is not rewind (pos %d)", pos) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = b.Backend.Save(ctx, h, rd) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 	_, err = seeker.Seek(pos, io.SeekStart) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 		return errors.Wrapf(err, "Seek") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = b.Cache.Save(h, rd) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		debug.Log("unable to save %v to cache: %v", h, err) | 
					
						
							| 
									
										
										
										
											2017-09-24 22:24:01 +02:00
										 |  |  | 		_ = b.Cache.Remove(h) | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-17 21:58:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var autoCacheFiles = map[restic.FileType]bool{ | 
					
						
							|  |  |  | 	restic.IndexFile:    true, | 
					
						
							|  |  |  | 	restic.SnapshotFile: true, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | func (b *Backend) cacheFile(ctx context.Context, h restic.Handle) error { | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 	finish := make(chan struct{}) | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		close(finish) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// remove the finish channel from the map | 
					
						
							|  |  |  | 		b.inProgressMutex.Lock() | 
					
						
							|  |  |  | 		delete(b.inProgress, h) | 
					
						
							|  |  |  | 		b.inProgressMutex.Unlock() | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	b.inProgressMutex.Lock() | 
					
						
							|  |  |  | 	other, alreadyDownloading := b.inProgress[h] | 
					
						
							|  |  |  | 	if !alreadyDownloading { | 
					
						
							|  |  |  | 		b.inProgress[h] = finish | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b.inProgressMutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if alreadyDownloading { | 
					
						
							|  |  |  | 		debug.Log("readahead %v is already performed by somebody else, delegating...", h) | 
					
						
							|  |  |  | 		<-other | 
					
						
							|  |  |  | 		debug.Log("download %v finished", h) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 	rd, err := b.Backend.Load(ctx, h, 0, 0) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = b.Cache.Save(h, rd); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = rd.Close(); err != nil { | 
					
						
							|  |  |  | 		// try to remove from the cache, ignore errors | 
					
						
							|  |  |  | 		_ = b.Cache.Remove(h) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | // loadFromCacheOrDelegate will try to load the file from the cache, and fall | 
					
						
							|  |  |  | // back to the backend if that fails. | 
					
						
							|  |  |  | func (b *Backend) loadFromCacheOrDelegate(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { | 
					
						
							|  |  |  | 	rd, err := b.Cache.Load(h, length, offset) | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		return rd, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return b.Backend.Load(ctx, h, length, offset) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | // Load loads a file from the cache or the backend. | 
					
						
							|  |  |  | func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 	b.inProgressMutex.Lock() | 
					
						
							|  |  |  | 	waitForFinish, inProgress := b.inProgress[h] | 
					
						
							|  |  |  | 	b.inProgressMutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if inProgress { | 
					
						
							|  |  |  | 		debug.Log("downloading %v is already in progress, waiting for finish", h) | 
					
						
							|  |  |  | 		<-waitForFinish | 
					
						
							|  |  |  | 		debug.Log("downloading %v finished", h) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	if b.Cache.Has(h) { | 
					
						
							|  |  |  | 		debug.Log("Load(%v, %v, %v) from cache", h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-09-24 23:06:54 +02:00
										 |  |  | 		rd, err := b.Cache.Load(h, length, offset) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			return rd, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		debug.Log("error loading %v from cache: %v", h, err) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:24:01 +02:00
										 |  |  | 	// partial file requested | 
					
						
							|  |  |  | 	if offset != 0 || length != 0 { | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 		if b.Cache.PerformReadahead(h) { | 
					
						
							|  |  |  | 			debug.Log("performing readahead for %v", h) | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 			err := b.cacheFile(ctx, h) | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							| 
									
										
										
										
											2017-09-25 15:21:16 +02:00
										 |  |  | 				return b.loadFromCacheOrDelegate(ctx, h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			debug.Log("error caching %v: %v", h, err) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 		debug.Log("Load(%v, %v, %v): partial file requested, delegating to backend", h, length, offset) | 
					
						
							|  |  |  | 		return b.Backend.Load(ctx, h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 	// if we don't automatically cache this file type, fall back to the backend | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	if _, ok := autoCacheFiles[h.Type]; !ok { | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 		debug.Log("Load(%v, %v, %v): delegating to backend", h, length, offset) | 
					
						
							|  |  |  | 		return b.Backend.Load(ctx, h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("auto-store %v in the cache", h) | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 	err := b.cacheFile(ctx, h) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		// load the cached version | 
					
						
							|  |  |  | 		return b.Cache.Load(h, 0, 0) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 22:50:35 +02:00
										 |  |  | 	debug.Log("error caching %v: %v, falling back to backend", h, err) | 
					
						
							|  |  |  | 	return b.Backend.Load(ctx, h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-06-10 13:10:08 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Stat tests whether the backend has a file. If it does not exist but still | 
					
						
							|  |  |  | // exists in the cache, it is removed from the cache. | 
					
						
							|  |  |  | func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) { | 
					
						
							|  |  |  | 	debug.Log("cache Stat(%v)", h) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fi, err := b.Backend.Stat(ctx, h) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if b.Backend.IsNotExist(err) { | 
					
						
							|  |  |  | 			// try to remove from the cache, ignore errors | 
					
						
							|  |  |  | 			_ = b.Cache.Remove(h) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return fi, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return fi, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsNotExist returns true if the error is caused by a non-existing file. | 
					
						
							|  |  |  | func (b *Backend) IsNotExist(err error) bool { | 
					
						
							|  |  |  | 	return b.Backend.IsNotExist(err) | 
					
						
							|  |  |  | } |