1package swift
2
3import (
4	"bytes"
5	"encoding/json"
6	"errors"
7	"fmt"
8	"io/ioutil"
9	"net/url"
10	"os"
11)
12
13// StaticLargeObjectCreateFile represents an open static large object
14type StaticLargeObjectCreateFile struct {
15	largeObjectCreateFile
16}
17
18var SLONotSupported = errors.New("SLO not supported")
19
20type swiftSegment struct {
21	Path string `json:"path,omitempty"`
22	Etag string `json:"etag,omitempty"`
23	Size int64  `json:"size_bytes,omitempty"`
24	// When uploading a manifest, the attributes must be named `path`, `etag` and `size_bytes`
25	// but when querying the JSON content of a manifest with the `multipart-manifest=get`
26	// parameter, Swift names those attributes `name`, `hash` and `bytes`.
27	// We use all the different attributes names in this structure to be able to use
28	// the same structure for both uploading and retrieving.
29	Name         string `json:"name,omitempty"`
30	Hash         string `json:"hash,omitempty"`
31	Bytes        int64  `json:"bytes,omitempty"`
32	ContentType  string `json:"content_type,omitempty"`
33	LastModified string `json:"last_modified,omitempty"`
34}
35
36// StaticLargeObjectCreateFile creates a static large object returning
37// an object which satisfies io.Writer, io.Seeker, io.Closer and
38// io.ReaderFrom.  The flags are as passed to the largeObjectCreate
39// method.
40func (c *Connection) StaticLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) {
41	info, err := c.cachedQueryInfo()
42	if err != nil || !info.SupportsSLO() {
43		return nil, SLONotSupported
44	}
45	realMinChunkSize := info.SLOMinSegmentSize()
46	if realMinChunkSize > opts.MinChunkSize {
47		opts.MinChunkSize = realMinChunkSize
48	}
49	lo, err := c.largeObjectCreate(opts)
50	if err != nil {
51		return nil, err
52	}
53	return withBuffer(opts, &StaticLargeObjectCreateFile{
54		largeObjectCreateFile: *lo,
55	}), nil
56}
57
58// StaticLargeObjectCreate creates or truncates an existing static
59// large object returning a writeable object. This sets opts.Flags to
60// an appropriate value before calling StaticLargeObjectCreateFile
61func (c *Connection) StaticLargeObjectCreate(opts *LargeObjectOpts) (LargeObjectFile, error) {
62	opts.Flags = os.O_TRUNC | os.O_CREATE
63	return c.StaticLargeObjectCreateFile(opts)
64}
65
66// StaticLargeObjectDelete deletes a static large object and all of its segments.
67func (c *Connection) StaticLargeObjectDelete(container string, path string) error {
68	info, err := c.cachedQueryInfo()
69	if err != nil || !info.SupportsSLO() {
70		return SLONotSupported
71	}
72	return c.LargeObjectDelete(container, path)
73}
74
75// StaticLargeObjectMove moves a static large object from srcContainer, srcObjectName to dstContainer, dstObjectName
76func (c *Connection) StaticLargeObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error {
77	swiftInfo, err := c.cachedQueryInfo()
78	if err != nil || !swiftInfo.SupportsSLO() {
79		return SLONotSupported
80	}
81	info, headers, err := c.Object(srcContainer, srcObjectName)
82	if err != nil {
83		return err
84	}
85
86	container, segments, err := c.getAllSegments(srcContainer, srcObjectName, headers)
87	if err != nil {
88		return err
89	}
90
91	//copy only metadata during move (other headers might not be safe for copying)
92	headers = headers.ObjectMetadata().ObjectHeaders()
93
94	if err := c.createSLOManifest(dstContainer, dstObjectName, info.ContentType, container, segments, headers); err != nil {
95		return err
96	}
97
98	if err := c.ObjectDelete(srcContainer, srcObjectName); err != nil {
99		return err
100	}
101
102	return nil
103}
104
105// createSLOManifest creates a static large object manifest
106func (c *Connection) createSLOManifest(container string, path string, contentType string, segmentContainer string, segments []Object, h Headers) error {
107	sloSegments := make([]swiftSegment, len(segments))
108	for i, segment := range segments {
109		sloSegments[i].Path = fmt.Sprintf("%s/%s", segmentContainer, segment.Name)
110		sloSegments[i].Etag = segment.Hash
111		sloSegments[i].Size = segment.Bytes
112	}
113
114	content, err := json.Marshal(sloSegments)
115	if err != nil {
116		return err
117	}
118
119	values := url.Values{}
120	values.Set("multipart-manifest", "put")
121	if _, err := c.objectPut(container, path, bytes.NewBuffer(content), false, "", contentType, h, values); err != nil {
122		return err
123	}
124
125	return nil
126}
127
128func (file *StaticLargeObjectCreateFile) Close() error {
129	return file.Flush()
130}
131
132func (file *StaticLargeObjectCreateFile) Flush() error {
133	if err := file.conn.createSLOManifest(file.container, file.objectName, file.contentType, file.segmentContainer, file.segments, file.headers); err != nil {
134		return err
135	}
136	return file.conn.waitForSegmentsToShowUp(file.container, file.objectName, file.Size())
137}
138
139func (c *Connection) getAllSLOSegments(container, path string) (string, []Object, error) {
140	var (
141		segmentList      []swiftSegment
142		segments         []Object
143		segPath          string
144		segmentContainer string
145	)
146
147	values := url.Values{}
148	values.Set("multipart-manifest", "get")
149
150	file, _, err := c.objectOpen(container, path, true, nil, values)
151	if err != nil {
152		return "", nil, err
153	}
154
155	content, err := ioutil.ReadAll(file)
156	if err != nil {
157		return "", nil, err
158	}
159
160	json.Unmarshal(content, &segmentList)
161	for _, segment := range segmentList {
162		segmentContainer, segPath = parseFullPath(segment.Name[1:])
163		segments = append(segments, Object{
164			Name:  segPath,
165			Bytes: segment.Bytes,
166			Hash:  segment.Hash,
167		})
168	}
169
170	return segmentContainer, segments, nil
171}
172