1package storage
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"fmt"
19	"net/http"
20	"net/url"
21	"strconv"
22)
23
24// Share represents an Azure file share.
25type Share struct {
26	fsc        *FileServiceClient
27	Name       string          `xml:"Name"`
28	Properties ShareProperties `xml:"Properties"`
29	Metadata   map[string]string
30}
31
32// ShareProperties contains various properties of a share.
33type ShareProperties struct {
34	LastModified string `xml:"Last-Modified"`
35	Etag         string `xml:"Etag"`
36	Quota        int    `xml:"Quota"`
37}
38
39// builds the complete path for this share object.
40func (s *Share) buildPath() string {
41	return fmt.Sprintf("/%s", s.Name)
42}
43
44// Create this share under the associated account.
45// If a share with the same name already exists, the operation fails.
46//
47// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Share
48func (s *Share) Create(options *FileRequestOptions) error {
49	extraheaders := map[string]string{}
50	if s.Properties.Quota > 0 {
51		extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
52	}
53
54	params := prepareOptions(options)
55	headers, err := s.fsc.createResource(s.buildPath(), resourceShare, params, mergeMDIntoExtraHeaders(s.Metadata, extraheaders), []int{http.StatusCreated})
56	if err != nil {
57		return err
58	}
59
60	s.updateEtagAndLastModified(headers)
61	return nil
62}
63
64// CreateIfNotExists creates this share under the associated account if
65// it does not exist. Returns true if the share is newly created or false if
66// the share already exists.
67//
68// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Share
69func (s *Share) CreateIfNotExists(options *FileRequestOptions) (bool, error) {
70	extraheaders := map[string]string{}
71	if s.Properties.Quota > 0 {
72		extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
73	}
74
75	params := prepareOptions(options)
76	resp, err := s.fsc.createResourceNoClose(s.buildPath(), resourceShare, params, extraheaders)
77	if resp != nil {
78		defer drainRespBody(resp)
79		if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
80			if resp.StatusCode == http.StatusCreated {
81				s.updateEtagAndLastModified(resp.Header)
82				return true, nil
83			}
84			return false, s.FetchAttributes(nil)
85		}
86	}
87
88	return false, err
89}
90
91// Delete marks this share for deletion. The share along with any files
92// and directories contained within it are later deleted during garbage
93// collection.  If the share does not exist the operation fails
94//
95// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Share
96func (s *Share) Delete(options *FileRequestOptions) error {
97	return s.fsc.deleteResource(s.buildPath(), resourceShare, options)
98}
99
100// DeleteIfExists operation marks this share for deletion if it exists.
101//
102// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Share
103func (s *Share) DeleteIfExists(options *FileRequestOptions) (bool, error) {
104	resp, err := s.fsc.deleteResourceNoClose(s.buildPath(), resourceShare, options)
105	if resp != nil {
106		defer drainRespBody(resp)
107		if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
108			return resp.StatusCode == http.StatusAccepted, nil
109		}
110	}
111	return false, err
112}
113
114// Exists returns true if this share already exists
115// on the storage account, otherwise returns false.
116func (s *Share) Exists() (bool, error) {
117	exists, headers, err := s.fsc.resourceExists(s.buildPath(), resourceShare)
118	if exists {
119		s.updateEtagAndLastModified(headers)
120		s.updateQuota(headers)
121	}
122	return exists, err
123}
124
125// FetchAttributes retrieves metadata and properties for this share.
126// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-share-properties
127func (s *Share) FetchAttributes(options *FileRequestOptions) error {
128	params := prepareOptions(options)
129	headers, err := s.fsc.getResourceHeaders(s.buildPath(), compNone, resourceShare, params, http.MethodHead)
130	if err != nil {
131		return err
132	}
133
134	s.updateEtagAndLastModified(headers)
135	s.updateQuota(headers)
136	s.Metadata = getMetadataFromHeaders(headers)
137
138	return nil
139}
140
141// GetRootDirectoryReference returns a Directory object at the root of this share.
142func (s *Share) GetRootDirectoryReference() *Directory {
143	return &Directory{
144		fsc:   s.fsc,
145		share: s,
146	}
147}
148
149// ServiceClient returns the FileServiceClient associated with this share.
150func (s *Share) ServiceClient() *FileServiceClient {
151	return s.fsc
152}
153
154// SetMetadata replaces the metadata for this share.
155//
156// Some keys may be converted to Camel-Case before sending. All keys
157// are returned in lower case by GetShareMetadata. HTTP header names
158// are case-insensitive so case munging should not matter to other
159// applications either.
160//
161// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-share-metadata
162func (s *Share) SetMetadata(options *FileRequestOptions) error {
163	headers, err := s.fsc.setResourceHeaders(s.buildPath(), compMetadata, resourceShare, mergeMDIntoExtraHeaders(s.Metadata, nil), options)
164	if err != nil {
165		return err
166	}
167
168	s.updateEtagAndLastModified(headers)
169	return nil
170}
171
172// SetProperties sets system properties for this share.
173//
174// Some keys may be converted to Camel-Case before sending. All keys
175// are returned in lower case by SetShareProperties. HTTP header names
176// are case-insensitive so case munging should not matter to other
177// applications either.
178//
179// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Share-Properties
180func (s *Share) SetProperties(options *FileRequestOptions) error {
181	extraheaders := map[string]string{}
182	if s.Properties.Quota > 0 {
183		if s.Properties.Quota > 5120 {
184			return fmt.Errorf("invalid value %v for quota, valid values are [1, 5120]", s.Properties.Quota)
185		}
186		extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
187	}
188
189	headers, err := s.fsc.setResourceHeaders(s.buildPath(), compProperties, resourceShare, extraheaders, options)
190	if err != nil {
191		return err
192	}
193
194	s.updateEtagAndLastModified(headers)
195	return nil
196}
197
198// updates Etag and last modified date
199func (s *Share) updateEtagAndLastModified(headers http.Header) {
200	s.Properties.Etag = headers.Get("Etag")
201	s.Properties.LastModified = headers.Get("Last-Modified")
202}
203
204// updates quota value
205func (s *Share) updateQuota(headers http.Header) {
206	quota, err := strconv.Atoi(headers.Get("x-ms-share-quota"))
207	if err == nil {
208		s.Properties.Quota = quota
209	}
210}
211
212// URL gets the canonical URL to this share. This method does not create a publicly accessible
213// URL if the share is private and this method does not check if the share exists.
214func (s *Share) URL() string {
215	return s.fsc.client.getEndpoint(fileServiceName, s.buildPath(), url.Values{})
216}
217