1// Metadata manipulation in and out of Headers 2 3package swift 4 5import ( 6 "fmt" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11) 12 13// Metadata stores account, container or object metadata. 14type Metadata map[string]string 15 16// Metadata gets the Metadata starting with the metaPrefix out of the Headers. 17// 18// The keys in the Metadata will be converted to lower case 19func (h Headers) Metadata(metaPrefix string) Metadata { 20 m := Metadata{} 21 metaPrefix = http.CanonicalHeaderKey(metaPrefix) 22 for key, value := range h { 23 if strings.HasPrefix(key, metaPrefix) { 24 metaKey := strings.ToLower(key[len(metaPrefix):]) 25 m[metaKey] = value 26 } 27 } 28 return m 29} 30 31// AccountMetadata converts Headers from account to a Metadata. 32// 33// The keys in the Metadata will be converted to lower case. 34func (h Headers) AccountMetadata() Metadata { 35 return h.Metadata("X-Account-Meta-") 36} 37 38// ContainerMetadata converts Headers from container to a Metadata. 39// 40// The keys in the Metadata will be converted to lower case. 41func (h Headers) ContainerMetadata() Metadata { 42 return h.Metadata("X-Container-Meta-") 43} 44 45// ObjectMetadata converts Headers from object to a Metadata. 46// 47// The keys in the Metadata will be converted to lower case. 48func (h Headers) ObjectMetadata() Metadata { 49 return h.Metadata("X-Object-Meta-") 50} 51 52// Headers convert the Metadata starting with the metaPrefix into a 53// Headers. 54// 55// The keys in the Metadata will be converted from lower case to http 56// Canonical (see http.CanonicalHeaderKey). 57func (m Metadata) Headers(metaPrefix string) Headers { 58 h := Headers{} 59 for key, value := range m { 60 key = http.CanonicalHeaderKey(metaPrefix + key) 61 h[key] = value 62 } 63 return h 64} 65 66// AccountHeaders converts the Metadata for the account. 67func (m Metadata) AccountHeaders() Headers { 68 return m.Headers("X-Account-Meta-") 69} 70 71// ContainerHeaders converts the Metadata for the container. 72func (m Metadata) ContainerHeaders() Headers { 73 return m.Headers("X-Container-Meta-") 74} 75 76// ObjectHeaders converts the Metadata for the object. 77func (m Metadata) ObjectHeaders() Headers { 78 return m.Headers("X-Object-Meta-") 79} 80 81// Turns a number of ns into a floating point string in seconds 82// 83// Trims trailing zeros and guaranteed to be perfectly accurate 84func nsToFloatString(ns int64) string { 85 if ns < 0 { 86 return "-" + nsToFloatString(-ns) 87 } 88 result := fmt.Sprintf("%010d", ns) 89 split := len(result) - 9 90 result, decimals := result[:split], result[split:] 91 decimals = strings.TrimRight(decimals, "0") 92 if decimals != "" { 93 result += "." 94 result += decimals 95 } 96 return result 97} 98 99// Turns a floating point string in seconds into a ns integer 100// 101// Guaranteed to be perfectly accurate 102func floatStringToNs(s string) (int64, error) { 103 const zeros = "000000000" 104 if point := strings.IndexRune(s, '.'); point >= 0 { 105 tail := s[point+1:] 106 if fill := 9 - len(tail); fill < 0 { 107 tail = tail[:9] 108 } else { 109 tail += zeros[:fill] 110 } 111 s = s[:point] + tail 112 } else if len(s) > 0 { // Make sure empty string produces an error 113 s += zeros 114 } 115 return strconv.ParseInt(s, 10, 64) 116} 117 118// FloatStringToTime converts a floating point number string to a time.Time 119// 120// The string is floating point number of seconds since the epoch 121// (Unix time). The number should be in fixed point format (not 122// exponential), eg "1354040105.123456789" which represents the time 123// "2012-11-27T18:15:05.123456789Z" 124// 125// Some care is taken to preserve all the accuracy in the time.Time 126// (which wouldn't happen with a naive conversion through float64) so 127// a round trip conversion won't change the data. 128// 129// If an error is returned then time will be returned as the zero time. 130func FloatStringToTime(s string) (t time.Time, err error) { 131 ns, err := floatStringToNs(s) 132 if err != nil { 133 return 134 } 135 t = time.Unix(0, ns) 136 return 137} 138 139// TimeToFloatString converts a time.Time object to a floating point string 140// 141// The string is floating point number of seconds since the epoch 142// (Unix time). The number is in fixed point format (not 143// exponential), eg "1354040105.123456789" which represents the time 144// "2012-11-27T18:15:05.123456789Z". Trailing zeros will be dropped 145// from the output. 146// 147// Some care is taken to preserve all the accuracy in the time.Time 148// (which wouldn't happen with a naive conversion through float64) so 149// a round trip conversion won't change the data. 150func TimeToFloatString(t time.Time) string { 151 return nsToFloatString(t.UnixNano()) 152} 153 154// Read a modification time (mtime) from a Metadata object 155// 156// This is a defacto standard (used in the official python-swiftclient 157// amongst others) for storing the modification time (as read using 158// os.Stat) for an object. It is stored using the key 'mtime', which 159// for example when written to an object will be 'X-Object-Meta-Mtime'. 160// 161// If an error is returned then time will be returned as the zero time. 162func (m Metadata) GetModTime() (t time.Time, err error) { 163 return FloatStringToTime(m["mtime"]) 164} 165 166// Write an modification time (mtime) to a Metadata object 167// 168// This is a defacto standard (used in the official python-swiftclient 169// amongst others) for storing the modification time (as read using 170// os.Stat) for an object. It is stored using the key 'mtime', which 171// for example when written to an object will be 'X-Object-Meta-Mtime'. 172func (m Metadata) SetModTime(t time.Time) { 173 m["mtime"] = TimeToFloatString(t) 174} 175