mirror of
https://github.com/octoleo/restic.git
synced 2024-11-18 11:05:18 +00:00
137 lines
4.5 KiB
Go
137 lines
4.5 KiB
Go
|
package swift
|
||
|
|
||
|
import (
|
||
|
"os"
|
||
|
)
|
||
|
|
||
|
// DynamicLargeObjectCreateFile represents an open static large object
|
||
|
type DynamicLargeObjectCreateFile struct {
|
||
|
largeObjectCreateFile
|
||
|
}
|
||
|
|
||
|
// DynamicLargeObjectCreateFile creates a dynamic large object
|
||
|
// returning an object which satisfies io.Writer, io.Seeker, io.Closer
|
||
|
// and io.ReaderFrom. The flags are as passes to the
|
||
|
// largeObjectCreate method.
|
||
|
func (c *Connection) DynamicLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) {
|
||
|
lo, err := c.largeObjectCreate(opts)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return withBuffer(opts, &DynamicLargeObjectCreateFile{
|
||
|
largeObjectCreateFile: *lo,
|
||
|
}), nil
|
||
|
}
|
||
|
|
||
|
// DynamicLargeObjectCreate creates or truncates an existing dynamic
|
||
|
// large object returning a writeable object. This sets opts.Flags to
|
||
|
// an appropriate value before calling DynamicLargeObjectCreateFile
|
||
|
func (c *Connection) DynamicLargeObjectCreate(opts *LargeObjectOpts) (LargeObjectFile, error) {
|
||
|
opts.Flags = os.O_TRUNC | os.O_CREATE
|
||
|
return c.DynamicLargeObjectCreateFile(opts)
|
||
|
}
|
||
|
|
||
|
// DynamicLargeObjectDelete deletes a dynamic large object and all of its segments.
|
||
|
func (c *Connection) DynamicLargeObjectDelete(container string, path string) error {
|
||
|
return c.LargeObjectDelete(container, path)
|
||
|
}
|
||
|
|
||
|
// DynamicLargeObjectMove moves a dynamic large object from srcContainer, srcObjectName to dstContainer, dstObjectName
|
||
|
func (c *Connection) DynamicLargeObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error {
|
||
|
info, headers, err := c.Object(dstContainer, srcObjectName)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
segmentContainer, segmentPath := parseFullPath(headers["X-Object-Manifest"])
|
||
|
if err := c.createDLOManifest(dstContainer, dstObjectName, segmentContainer+"/"+segmentPath, info.ContentType); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := c.ObjectDelete(srcContainer, srcObjectName); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// createDLOManifest creates a dynamic large object manifest
|
||
|
func (c *Connection) createDLOManifest(container string, objectName string, prefix string, contentType string) error {
|
||
|
headers := make(Headers)
|
||
|
headers["X-Object-Manifest"] = prefix
|
||
|
manifest, err := c.ObjectCreate(container, objectName, false, "", contentType, headers)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := manifest.Close(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close satisfies the io.Closer interface
|
||
|
func (file *DynamicLargeObjectCreateFile) Close() error {
|
||
|
return file.Flush()
|
||
|
}
|
||
|
|
||
|
func (file *DynamicLargeObjectCreateFile) Flush() error {
|
||
|
err := file.conn.createDLOManifest(file.container, file.objectName, file.segmentContainer+"/"+file.prefix, file.contentType)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return file.conn.waitForSegmentsToShowUp(file.container, file.objectName, file.Size())
|
||
|
}
|
||
|
|
||
|
func (c *Connection) getAllDLOSegments(segmentContainer, segmentPath string) ([]Object, error) {
|
||
|
//a simple container listing works 99.9% of the time
|
||
|
segments, err := c.ObjectsAll(segmentContainer, &ObjectsOpts{Prefix: segmentPath})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
hasObjectName := make(map[string]struct{})
|
||
|
for _, segment := range segments {
|
||
|
hasObjectName[segment.Name] = struct{}{}
|
||
|
}
|
||
|
|
||
|
//The container listing might be outdated (i.e. not contain all existing
|
||
|
//segment objects yet) because of temporary inconsistency (Swift is only
|
||
|
//eventually consistent!). Check its completeness.
|
||
|
segmentNumber := 0
|
||
|
for {
|
||
|
segmentNumber++
|
||
|
segmentName := getSegment(segmentPath, segmentNumber)
|
||
|
if _, seen := hasObjectName[segmentName]; seen {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
//This segment is missing in the container listing. Use a more reliable
|
||
|
//request to check its existence. (HEAD requests on segments are
|
||
|
//guaranteed to return the correct metadata, except for the pathological
|
||
|
//case of an outage of large parts of the Swift cluster or its network,
|
||
|
//since every segment is only written once.)
|
||
|
segment, _, err := c.Object(segmentContainer, segmentName)
|
||
|
switch err {
|
||
|
case nil:
|
||
|
//found new segment -> add it in the correct position and keep
|
||
|
//going, more might be missing
|
||
|
if segmentNumber <= len(segments) {
|
||
|
segments = append(segments[:segmentNumber], segments[segmentNumber-1:]...)
|
||
|
segments[segmentNumber-1] = segment
|
||
|
} else {
|
||
|
segments = append(segments, segment)
|
||
|
}
|
||
|
continue
|
||
|
case ObjectNotFound:
|
||
|
//This segment is missing. Since we upload segments sequentially,
|
||
|
//there won't be any more segments after it.
|
||
|
return segments, nil
|
||
|
default:
|
||
|
return nil, err //unexpected error
|
||
|
}
|
||
|
}
|
||
|
}
|