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 "encoding/xml" 19 "net/http" 20 "net/url" 21 "sync" 22) 23 24// Directory represents a directory on a share. 25type Directory struct { 26 fsc *FileServiceClient 27 Metadata map[string]string 28 Name string `xml:"Name"` 29 parent *Directory 30 Properties DirectoryProperties 31 share *Share 32} 33 34// DirectoryProperties contains various properties of a directory. 35type DirectoryProperties struct { 36 LastModified string `xml:"Last-Modified"` 37 Etag string `xml:"Etag"` 38} 39 40// ListDirsAndFilesParameters defines the set of customizable parameters to 41// make a List Files and Directories call. 42// 43// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files 44type ListDirsAndFilesParameters struct { 45 Prefix string 46 Marker string 47 MaxResults uint 48 Timeout uint 49} 50 51// DirsAndFilesListResponse contains the response fields from 52// a List Files and Directories call. 53// 54// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files 55type DirsAndFilesListResponse struct { 56 XMLName xml.Name `xml:"EnumerationResults"` 57 Xmlns string `xml:"xmlns,attr"` 58 Marker string `xml:"Marker"` 59 MaxResults int64 `xml:"MaxResults"` 60 Directories []Directory `xml:"Entries>Directory"` 61 Files []File `xml:"Entries>File"` 62 NextMarker string `xml:"NextMarker"` 63} 64 65// builds the complete directory path for this directory object. 66func (d *Directory) buildPath() string { 67 path := "" 68 current := d 69 for current.Name != "" { 70 path = "/" + current.Name + path 71 current = current.parent 72 } 73 return d.share.buildPath() + path 74} 75 76// Create this directory in the associated share. 77// If a directory with the same name already exists, the operation fails. 78// 79// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory 80func (d *Directory) Create(options *FileRequestOptions) error { 81 // if this is the root directory exit early 82 if d.parent == nil { 83 return nil 84 } 85 86 params := prepareOptions(options) 87 headers, err := d.fsc.createResource(d.buildPath(), resourceDirectory, params, mergeMDIntoExtraHeaders(d.Metadata, nil), []int{http.StatusCreated}) 88 if err != nil { 89 return err 90 } 91 92 d.updateEtagAndLastModified(headers) 93 return nil 94} 95 96// CreateIfNotExists creates this directory under the associated share if the 97// directory does not exists. Returns true if the directory is newly created or 98// false if the directory already exists. 99// 100// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory 101func (d *Directory) CreateIfNotExists(options *FileRequestOptions) (bool, error) { 102 // if this is the root directory exit early 103 if d.parent == nil { 104 return false, nil 105 } 106 107 params := prepareOptions(options) 108 resp, err := d.fsc.createResourceNoClose(d.buildPath(), resourceDirectory, params, nil) 109 if resp != nil { 110 defer drainRespBody(resp) 111 if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict { 112 if resp.StatusCode == http.StatusCreated { 113 d.updateEtagAndLastModified(resp.Header) 114 return true, nil 115 } 116 117 return false, d.FetchAttributes(nil) 118 } 119 } 120 121 return false, err 122} 123 124// Delete removes this directory. It must be empty in order to be deleted. 125// If the directory does not exist the operation fails. 126// 127// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory 128func (d *Directory) Delete(options *FileRequestOptions) error { 129 return d.fsc.deleteResource(d.buildPath(), resourceDirectory, options) 130} 131 132// DeleteIfExists removes this directory if it exists. 133// 134// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory 135func (d *Directory) DeleteIfExists(options *FileRequestOptions) (bool, error) { 136 resp, err := d.fsc.deleteResourceNoClose(d.buildPath(), resourceDirectory, options) 137 if resp != nil { 138 defer drainRespBody(resp) 139 if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound { 140 return resp.StatusCode == http.StatusAccepted, nil 141 } 142 } 143 return false, err 144} 145 146// Exists returns true if this directory exists. 147func (d *Directory) Exists() (bool, error) { 148 exists, headers, err := d.fsc.resourceExists(d.buildPath(), resourceDirectory) 149 if exists { 150 d.updateEtagAndLastModified(headers) 151 } 152 return exists, err 153} 154 155// FetchAttributes retrieves metadata for this directory. 156// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-directory-properties 157func (d *Directory) FetchAttributes(options *FileRequestOptions) error { 158 params := prepareOptions(options) 159 headers, err := d.fsc.getResourceHeaders(d.buildPath(), compNone, resourceDirectory, params, http.MethodHead) 160 if err != nil { 161 return err 162 } 163 164 d.updateEtagAndLastModified(headers) 165 d.Metadata = getMetadataFromHeaders(headers) 166 167 return nil 168} 169 170// GetDirectoryReference returns a child Directory object for this directory. 171func (d *Directory) GetDirectoryReference(name string) *Directory { 172 return &Directory{ 173 fsc: d.fsc, 174 Name: name, 175 parent: d, 176 share: d.share, 177 } 178} 179 180// GetFileReference returns a child File object for this directory. 181func (d *Directory) GetFileReference(name string) *File { 182 return &File{ 183 fsc: d.fsc, 184 Name: name, 185 parent: d, 186 share: d.share, 187 mutex: &sync.Mutex{}, 188 } 189} 190 191// ListDirsAndFiles returns a list of files and directories under this directory. 192// It also contains a pagination token and other response details. 193// 194// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files 195func (d *Directory) ListDirsAndFiles(params ListDirsAndFilesParameters) (*DirsAndFilesListResponse, error) { 196 q := mergeParams(params.getParameters(), getURLInitValues(compList, resourceDirectory)) 197 198 resp, err := d.fsc.listContent(d.buildPath(), q, nil) 199 if err != nil { 200 return nil, err 201 } 202 203 defer resp.Body.Close() 204 var out DirsAndFilesListResponse 205 err = xmlUnmarshal(resp.Body, &out) 206 return &out, err 207} 208 209// SetMetadata replaces the metadata for this directory. 210// 211// Some keys may be converted to Camel-Case before sending. All keys 212// are returned in lower case by GetDirectoryMetadata. HTTP header names 213// are case-insensitive so case munging should not matter to other 214// applications either. 215// 216// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Directory-Metadata 217func (d *Directory) SetMetadata(options *FileRequestOptions) error { 218 headers, err := d.fsc.setResourceHeaders(d.buildPath(), compMetadata, resourceDirectory, mergeMDIntoExtraHeaders(d.Metadata, nil), options) 219 if err != nil { 220 return err 221 } 222 223 d.updateEtagAndLastModified(headers) 224 return nil 225} 226 227// updates Etag and last modified date 228func (d *Directory) updateEtagAndLastModified(headers http.Header) { 229 d.Properties.Etag = headers.Get("Etag") 230 d.Properties.LastModified = headers.Get("Last-Modified") 231} 232 233// URL gets the canonical URL to this directory. 234// This method does not create a publicly accessible URL if the directory 235// is private and this method does not check if the directory exists. 236func (d *Directory) URL() string { 237 return d.fsc.client.getEndpoint(fileServiceName, d.buildPath(), url.Values{}) 238} 239