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