mirror of
https://github.com/restic/restic.git
synced 2025-03-23 00:00:04 +01:00
243 lines
5.1 KiB
Go
243 lines
5.1 KiB
Go
package rest
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/restic/restic/backend"
|
|
)
|
|
|
|
const connLimit = 10
|
|
|
|
// Returns the url of the resource
|
|
func restPath(url *url.URL, t backend.Type, name string) string {
|
|
ept := url.String()
|
|
if !strings.HasSuffix(ept, "/") {
|
|
ept += "/"
|
|
}
|
|
var dir string
|
|
switch t {
|
|
case backend.Config:
|
|
dir = backend.Paths.Config
|
|
case backend.Data:
|
|
dir = backend.Paths.Data
|
|
case backend.Snapshot:
|
|
dir = backend.Paths.Snapshots
|
|
case backend.Index:
|
|
dir = backend.Paths.Index
|
|
case backend.Lock:
|
|
dir = backend.Paths.Locks
|
|
case backend.Key:
|
|
dir = backend.Paths.Keys
|
|
default:
|
|
dir = string(t)
|
|
}
|
|
return ept + dir + "/" + name
|
|
}
|
|
|
|
type RestBlob struct {
|
|
b *Rest
|
|
buf *bytes.Buffer
|
|
final bool
|
|
}
|
|
|
|
func (rb *RestBlob) Write(p []byte) (int, error) {
|
|
if rb.final {
|
|
return 0, errors.New("blob already closed")
|
|
}
|
|
n, err := rb.buf.Write(p)
|
|
return n, err
|
|
}
|
|
|
|
func (rb *RestBlob) Read(p []byte) (int, error) {
|
|
return rb.buf.Read(p)
|
|
}
|
|
|
|
func (rb *RestBlob) Close() error {
|
|
rb.final = true
|
|
rb.buf.Reset()
|
|
return nil
|
|
}
|
|
|
|
// Finalize moves the data blob to the final location for type and name.
|
|
func (rb *RestBlob) Finalize(t backend.Type, name string) error {
|
|
if rb.final {
|
|
return errors.New("blob already finalized")
|
|
}
|
|
|
|
rb.final = true
|
|
|
|
<-rb.b.connChan
|
|
client := *rb.b.client
|
|
resp, err := client.Post(restPath(rb.b.url, t, name), "binary/octet-stream", rb.buf)
|
|
if resp != nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
err = errors.New("blob not saved")
|
|
}
|
|
rb.b.connChan <- struct{}{}
|
|
rb.buf.Reset()
|
|
|
|
return err
|
|
}
|
|
|
|
// Size returns the number of bytes written to the backend so far.
|
|
func (rb *RestBlob) Size() uint {
|
|
return uint(rb.buf.Len())
|
|
}
|
|
|
|
// A simple REST backend.
|
|
type Rest struct {
|
|
url *url.URL
|
|
connChan chan struct{}
|
|
client *http.Client
|
|
}
|
|
|
|
// Open opens the http backend at the specified url.
|
|
func Open(url *url.URL) (*Rest, error) {
|
|
connChan := make(chan struct{}, connLimit)
|
|
for i := 0; i < connLimit; i++ {
|
|
connChan <- struct{}{}
|
|
}
|
|
tr := &http.Transport{}
|
|
client := http.Client{Transport: tr}
|
|
return &Rest{url: url, connChan: connChan, client: &client}, nil
|
|
}
|
|
|
|
// Location returns a string that specifies the location of the repository, like a URL.
|
|
func (b *Rest) Location() string {
|
|
return b.url.Host
|
|
}
|
|
|
|
// Create creates a new Blob. The data is available only after Finalize()
|
|
// has been called on the returned Blob.
|
|
func (b *Rest) Create() (backend.Blob, error) {
|
|
blob := RestBlob{
|
|
b: b,
|
|
buf: &bytes.Buffer{},
|
|
}
|
|
return &blob, nil
|
|
}
|
|
|
|
// Get returns an io.ReadCloser for the Blob with the given name of type t.
|
|
func (b *Rest) Get(t backend.Type, name string) (io.ReadCloser, error) {
|
|
resp, err := b.client.Get(restPath(b.url, t, name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return nil, errors.New("blob not found")
|
|
}
|
|
return resp.Body, err
|
|
}
|
|
|
|
// GetReader returns an io.ReadCloser for the Blob with the given name of
|
|
// type t at offset and length.
|
|
func (b *Rest) GetReader(t backend.Type, name string, offset, length uint) (io.ReadCloser, error) {
|
|
req, err := http.NewRequest("GET", restPath(b.url, t, name), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length))
|
|
client := *b.client
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != 206 {
|
|
return nil, errors.New("blob not found")
|
|
}
|
|
return backend.LimitReadCloser(resp.Body, int64(length)), nil
|
|
}
|
|
|
|
// Test a boolean value whether a Blob with the name and type exists.
|
|
func (b *Rest) Test(t backend.Type, name string) (bool, error) {
|
|
req, err := http.NewRequest("HEAD", restPath(b.url, t, name), nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
client := *b.client
|
|
resp, err := client.Do(req)
|
|
if resp != nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Remove removes a Blob with type t and name.
|
|
func (b *Rest) Remove(t backend.Type, name string) error {
|
|
req, err := http.NewRequest("DELETE", restPath(b.url, t, name), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client := *b.client
|
|
resp, err := client.Do(req)
|
|
if resp != nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Close the backend
|
|
func (b *Rest) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// List returns a channel that yields all names of blobs of type t in
|
|
// lexicographic order. A goroutine is started for this. If the channel
|
|
// done is closed, sending stops.
|
|
func (b *Rest) List(t backend.Type, done <-chan struct{}) <-chan string {
|
|
ch := make(chan string)
|
|
|
|
client := *b.client
|
|
resp, err := client.Get(restPath(b.url, t, ""))
|
|
if resp != nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
if err != nil {
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
var list []string
|
|
errj := json.Unmarshal(data, &list)
|
|
if errj != nil {
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
sort.Strings(list)
|
|
for _, m := range list {
|
|
select {
|
|
case ch <- m:
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch
|
|
}
|