172 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
package swift
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
)
 | 
						|
 | 
						|
// StaticLargeObjectCreateFile represents an open static large object
 | 
						|
type StaticLargeObjectCreateFile struct {
 | 
						|
	largeObjectCreateFile
 | 
						|
}
 | 
						|
 | 
						|
var SLONotSupported = errors.New("SLO not supported")
 | 
						|
 | 
						|
type swiftSegment struct {
 | 
						|
	Path string `json:"path,omitempty"`
 | 
						|
	Etag string `json:"etag,omitempty"`
 | 
						|
	Size int64  `json:"size_bytes,omitempty"`
 | 
						|
	// When uploading a manifest, the attributes must be named `path`, `etag` and `size_bytes`
 | 
						|
	// but when querying the JSON content of a manifest with the `multipart-manifest=get`
 | 
						|
	// parameter, Swift names those attributes `name`, `hash` and `bytes`.
 | 
						|
	// We use all the different attributes names in this structure to be able to use
 | 
						|
	// the same structure for both uploading and retrieving.
 | 
						|
	Name         string `json:"name,omitempty"`
 | 
						|
	Hash         string `json:"hash,omitempty"`
 | 
						|
	Bytes        int64  `json:"bytes,omitempty"`
 | 
						|
	ContentType  string `json:"content_type,omitempty"`
 | 
						|
	LastModified string `json:"last_modified,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// StaticLargeObjectCreateFile creates a static large object returning
 | 
						|
// an object which satisfies io.Writer, io.Seeker, io.Closer and
 | 
						|
// io.ReaderFrom.  The flags are as passed to the largeObjectCreate
 | 
						|
// method.
 | 
						|
func (c *Connection) StaticLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) {
 | 
						|
	info, err := c.cachedQueryInfo()
 | 
						|
	if err != nil || !info.SupportsSLO() {
 | 
						|
		return nil, SLONotSupported
 | 
						|
	}
 | 
						|
	realMinChunkSize := info.SLOMinSegmentSize()
 | 
						|
	if realMinChunkSize > opts.MinChunkSize {
 | 
						|
		opts.MinChunkSize = realMinChunkSize
 | 
						|
	}
 | 
						|
	lo, err := c.largeObjectCreate(opts)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return withBuffer(opts, &StaticLargeObjectCreateFile{
 | 
						|
		largeObjectCreateFile: *lo,
 | 
						|
	}), nil
 | 
						|
}
 | 
						|
 | 
						|
// StaticLargeObjectCreate creates or truncates an existing static
 | 
						|
// large object returning a writeable object. This sets opts.Flags to
 | 
						|
// an appropriate value before calling StaticLargeObjectCreateFile
 | 
						|
func (c *Connection) StaticLargeObjectCreate(opts *LargeObjectOpts) (LargeObjectFile, error) {
 | 
						|
	opts.Flags = os.O_TRUNC | os.O_CREATE
 | 
						|
	return c.StaticLargeObjectCreateFile(opts)
 | 
						|
}
 | 
						|
 | 
						|
// StaticLargeObjectDelete deletes a static large object and all of its segments.
 | 
						|
func (c *Connection) StaticLargeObjectDelete(container string, path string) error {
 | 
						|
	info, err := c.cachedQueryInfo()
 | 
						|
	if err != nil || !info.SupportsSLO() {
 | 
						|
		return SLONotSupported
 | 
						|
	}
 | 
						|
	return c.LargeObjectDelete(container, path)
 | 
						|
}
 | 
						|
 | 
						|
// StaticLargeObjectMove moves a static large object from srcContainer, srcObjectName to dstContainer, dstObjectName
 | 
						|
func (c *Connection) StaticLargeObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error {
 | 
						|
	swiftInfo, err := c.cachedQueryInfo()
 | 
						|
	if err != nil || !swiftInfo.SupportsSLO() {
 | 
						|
		return SLONotSupported
 | 
						|
	}
 | 
						|
	info, headers, err := c.Object(srcContainer, srcObjectName)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	container, segments, err := c.getAllSegments(srcContainer, srcObjectName, headers)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	//copy only metadata during move (other headers might not be safe for copying)
 | 
						|
	headers = headers.ObjectMetadata().ObjectHeaders()
 | 
						|
 | 
						|
	if err := c.createSLOManifest(dstContainer, dstObjectName, info.ContentType, container, segments, headers); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.ObjectDelete(srcContainer, srcObjectName); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// createSLOManifest creates a static large object manifest
 | 
						|
func (c *Connection) createSLOManifest(container string, path string, contentType string, segmentContainer string, segments []Object, h Headers) error {
 | 
						|
	sloSegments := make([]swiftSegment, len(segments))
 | 
						|
	for i, segment := range segments {
 | 
						|
		sloSegments[i].Path = fmt.Sprintf("%s/%s", segmentContainer, segment.Name)
 | 
						|
		sloSegments[i].Etag = segment.Hash
 | 
						|
		sloSegments[i].Size = segment.Bytes
 | 
						|
	}
 | 
						|
 | 
						|
	content, err := json.Marshal(sloSegments)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	values := url.Values{}
 | 
						|
	values.Set("multipart-manifest", "put")
 | 
						|
	if _, err := c.objectPut(container, path, bytes.NewBuffer(content), false, "", contentType, h, values); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (file *StaticLargeObjectCreateFile) Close() error {
 | 
						|
	return file.Flush()
 | 
						|
}
 | 
						|
 | 
						|
func (file *StaticLargeObjectCreateFile) Flush() error {
 | 
						|
	if err := file.conn.createSLOManifest(file.container, file.objectName, file.contentType, file.segmentContainer, file.segments, file.headers); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return file.conn.waitForSegmentsToShowUp(file.container, file.objectName, file.Size())
 | 
						|
}
 | 
						|
 | 
						|
func (c *Connection) getAllSLOSegments(container, path string) (string, []Object, error) {
 | 
						|
	var (
 | 
						|
		segmentList      []swiftSegment
 | 
						|
		segments         []Object
 | 
						|
		segPath          string
 | 
						|
		segmentContainer string
 | 
						|
	)
 | 
						|
 | 
						|
	values := url.Values{}
 | 
						|
	values.Set("multipart-manifest", "get")
 | 
						|
 | 
						|
	file, _, err := c.objectOpen(container, path, true, nil, values)
 | 
						|
	if err != nil {
 | 
						|
		return "", nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	content, err := ioutil.ReadAll(file)
 | 
						|
	if err != nil {
 | 
						|
		return "", nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	json.Unmarshal(content, &segmentList)
 | 
						|
	for _, segment := range segmentList {
 | 
						|
		segmentContainer, segPath = parseFullPath(segment.Name[1:])
 | 
						|
		segments = append(segments, Object{
 | 
						|
			Name:  segPath,
 | 
						|
			Bytes: segment.Bytes,
 | 
						|
			Hash:  segment.Hash,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return segmentContainer, segments, nil
 | 
						|
}
 |