package storage // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/xml" "net/http" "net/url" "sync" ) // Directory represents a directory on a share. type Directory struct { fsc *FileServiceClient Metadata map[string]string Name string `xml:"Name"` parent *Directory Properties DirectoryProperties share *Share } // DirectoryProperties contains various properties of a directory. type DirectoryProperties struct { LastModified string `xml:"Last-Modified"` Etag string `xml:"Etag"` } // ListDirsAndFilesParameters defines the set of customizable parameters to // make a List Files and Directories call. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files type ListDirsAndFilesParameters struct { Prefix string Marker string MaxResults uint Timeout uint } // DirsAndFilesListResponse contains the response fields from // a List Files and Directories call. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files type DirsAndFilesListResponse struct { XMLName xml.Name `xml:"EnumerationResults"` Xmlns string `xml:"xmlns,attr"` Marker string `xml:"Marker"` MaxResults int64 `xml:"MaxResults"` Directories []Directory `xml:"Entries>Directory"` Files []File `xml:"Entries>File"` NextMarker string `xml:"NextMarker"` } // builds the complete directory path for this directory object. func (d *Directory) buildPath() string { path := "" current := d for current.Name != "" { path = "/" + current.Name + path current = current.parent } return d.share.buildPath() + path } // Create this directory in the associated share. // If a directory with the same name already exists, the operation fails. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory func (d *Directory) Create(options *FileRequestOptions) error { // if this is the root directory exit early if d.parent == nil { return nil } params := prepareOptions(options) headers, err := d.fsc.createResource(d.buildPath(), resourceDirectory, params, mergeMDIntoExtraHeaders(d.Metadata, nil), []int{http.StatusCreated}) if err != nil { return err } d.updateEtagAndLastModified(headers) return nil } // CreateIfNotExists creates this directory under the associated share if the // directory does not exists. Returns true if the directory is newly created or // false if the directory already exists. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory func (d *Directory) CreateIfNotExists(options *FileRequestOptions) (bool, error) { // if this is the root directory exit early if d.parent == nil { return false, nil } params := prepareOptions(options) resp, err := d.fsc.createResourceNoClose(d.buildPath(), resourceDirectory, params, nil) if resp != nil { defer drainRespBody(resp) if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict { if resp.StatusCode == http.StatusCreated { d.updateEtagAndLastModified(resp.Header) return true, nil } return false, d.FetchAttributes(nil) } } return false, err } // Delete removes this directory. It must be empty in order to be deleted. // If the directory does not exist the operation fails. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory func (d *Directory) Delete(options *FileRequestOptions) error { return d.fsc.deleteResource(d.buildPath(), resourceDirectory, options) } // DeleteIfExists removes this directory if it exists. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory func (d *Directory) DeleteIfExists(options *FileRequestOptions) (bool, error) { resp, err := d.fsc.deleteResourceNoClose(d.buildPath(), resourceDirectory, options) if resp != nil { defer drainRespBody(resp) if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound { return resp.StatusCode == http.StatusAccepted, nil } } return false, err } // Exists returns true if this directory exists. func (d *Directory) Exists() (bool, error) { exists, headers, err := d.fsc.resourceExists(d.buildPath(), resourceDirectory) if exists { d.updateEtagAndLastModified(headers) } return exists, err } // FetchAttributes retrieves metadata for this directory. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-directory-properties func (d *Directory) FetchAttributes(options *FileRequestOptions) error { params := prepareOptions(options) headers, err := d.fsc.getResourceHeaders(d.buildPath(), compNone, resourceDirectory, params, http.MethodHead) if err != nil { return err } d.updateEtagAndLastModified(headers) d.Metadata = getMetadataFromHeaders(headers) return nil } // GetDirectoryReference returns a child Directory object for this directory. func (d *Directory) GetDirectoryReference(name string) *Directory { return &Directory{ fsc: d.fsc, Name: name, parent: d, share: d.share, } } // GetFileReference returns a child File object for this directory. func (d *Directory) GetFileReference(name string) *File { return &File{ fsc: d.fsc, Name: name, parent: d, share: d.share, mutex: &sync.Mutex{}, } } // ListDirsAndFiles returns a list of files and directories under this directory. // It also contains a pagination token and other response details. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files func (d *Directory) ListDirsAndFiles(params ListDirsAndFilesParameters) (*DirsAndFilesListResponse, error) { q := mergeParams(params.getParameters(), getURLInitValues(compList, resourceDirectory)) resp, err := d.fsc.listContent(d.buildPath(), q, nil) if err != nil { return nil, err } defer resp.Body.Close() var out DirsAndFilesListResponse err = xmlUnmarshal(resp.Body, &out) return &out, err } // SetMetadata replaces the metadata for this directory. // // Some keys may be converted to Camel-Case before sending. All keys // are returned in lower case by GetDirectoryMetadata. HTTP header names // are case-insensitive so case munging should not matter to other // applications either. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Directory-Metadata func (d *Directory) SetMetadata(options *FileRequestOptions) error { headers, err := d.fsc.setResourceHeaders(d.buildPath(), compMetadata, resourceDirectory, mergeMDIntoExtraHeaders(d.Metadata, nil), options) if err != nil { return err } d.updateEtagAndLastModified(headers) return nil } // updates Etag and last modified date func (d *Directory) updateEtagAndLastModified(headers http.Header) { d.Properties.Etag = headers.Get("Etag") d.Properties.LastModified = headers.Get("Last-Modified") } // URL gets the canonical URL to this directory. // This method does not create a publicly accessible URL if the directory // is private and this method does not check if the directory exists. func (d *Directory) URL() string { return d.fsc.client.getEndpoint(fileServiceName, d.buildPath(), url.Values{}) }