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