1package azblob
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"net/url"
8	"strconv"
9
10	"github.com/Azure/azure-pipeline-go/pipeline"
11)
12
13const (
14	// PageBlobPageBytes indicates the number of bytes in a page (512).
15	PageBlobPageBytes = 512
16
17	// PageBlobMaxPutPagesBytes indicates the maximum number of bytes that can be sent in a call to PutPage.
18	PageBlobMaxUploadPagesBytes = 4 * 1024 * 1024 // 4MB
19)
20
21// PageBlobURL defines a set of operations applicable to page blobs.
22type PageBlobURL struct {
23	BlobURL
24	pbClient pageBlobClient
25}
26
27// NewPageBlobURL creates a PageBlobURL object using the specified URL and request policy pipeline.
28func NewPageBlobURL(url url.URL, p pipeline.Pipeline) PageBlobURL {
29	blobClient := newBlobClient(url, p)
30	pbClient := newPageBlobClient(url, p)
31	return PageBlobURL{BlobURL: BlobURL{blobClient: blobClient}, pbClient: pbClient}
32}
33
34// WithPipeline creates a new PageBlobURL object identical to the source but with the specific request policy pipeline.
35func (pb PageBlobURL) WithPipeline(p pipeline.Pipeline) PageBlobURL {
36	return NewPageBlobURL(pb.blobClient.URL(), p)
37}
38
39// WithSnapshot creates a new PageBlobURL object identical to the source but with the specified snapshot timestamp.
40// Pass "" to remove the snapshot returning a URL to the base blob.
41func (pb PageBlobURL) WithSnapshot(snapshot string) PageBlobURL {
42	p := NewBlobURLParts(pb.URL())
43	p.Snapshot = snapshot
44	return NewPageBlobURL(p.URL(), pb.blobClient.Pipeline())
45}
46
47// Create creates a page blob of the specified length. Call PutPage to upload data data to a page blob.
48// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-blob.
49func (pb PageBlobURL) Create(ctx context.Context, size int64, sequenceNumber int64, h BlobHTTPHeaders, metadata Metadata, ac BlobAccessConditions) (*PageBlobCreateResponse, error) {
50	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
51	return pb.pbClient.Create(ctx, 0, size, nil,
52		&h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5, &h.CacheControl,
53		metadata, ac.LeaseAccessConditions.pointers(),
54		&h.ContentDisposition, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, &sequenceNumber, nil)
55}
56
57// UploadPages writes 1 or more pages to the page blob. The start offset and the stream size must be a multiple of 512 bytes.
58// This method panics if the stream is not at position 0.
59// Note that the http client closes the body stream after the request is sent to the service.
60// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page.
61func (pb PageBlobURL) UploadPages(ctx context.Context, offset int64, body io.ReadSeeker, ac PageBlobAccessConditions, transactionalMD5 []byte) (*PageBlobUploadPagesResponse, error) {
62	count, err := validateSeekableStreamAt0AndGetCount(body)
63	if err != nil {
64		return nil, err
65	}
66	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
67	ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := ac.SequenceNumberAccessConditions.pointers()
68	return pb.pbClient.UploadPages(ctx, body, count, transactionalMD5, nil,
69		PageRange{Start: offset, End: offset + count - 1}.pointers(),
70		ac.LeaseAccessConditions.pointers(),
71		ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual,
72		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
73}
74
75// UploadPagesFromURL copies 1 or more pages from a source URL to the page blob.
76// The sourceOffset specifies the start offset of source data to copy from.
77// The destOffset specifies the start offset of data in page blob will be written to.
78// The count must be a multiple of 512 bytes.
79// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page-from-url.
80func (pb PageBlobURL) UploadPagesFromURL(ctx context.Context, sourceURL url.URL, sourceOffset int64, destOffset int64, count int64, transactionalMD5 []byte, destinationAccessConditions PageBlobAccessConditions, sourceAccessConditions ModifiedAccessConditions) (*PageBlobUploadPagesFromURLResponse, error) {
81	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := destinationAccessConditions.ModifiedAccessConditions.pointers()
82	sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag := sourceAccessConditions.pointers()
83	ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := destinationAccessConditions.SequenceNumberAccessConditions.pointers()
84	return pb.pbClient.UploadPagesFromURL(ctx, sourceURL.String(), *PageRange{Start: sourceOffset, End: sourceOffset + count - 1}.pointers(), 0,
85		*PageRange{Start: destOffset, End: destOffset + count - 1}.pointers(), transactionalMD5, nil, destinationAccessConditions.LeaseAccessConditions.pointers(),
86		ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual,
87		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag, nil)
88}
89
90// ClearPages frees the specified pages from the page blob.
91// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page.
92func (pb PageBlobURL) ClearPages(ctx context.Context, offset int64, count int64, ac PageBlobAccessConditions) (*PageBlobClearPagesResponse, error) {
93	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
94	ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := ac.SequenceNumberAccessConditions.pointers()
95	return pb.pbClient.ClearPages(ctx, 0, nil,
96		PageRange{Start: offset, End: offset + count - 1}.pointers(),
97		ac.LeaseAccessConditions.pointers(),
98		ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan,
99		ifSequenceNumberEqual, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
100}
101
102// GetPageRanges returns the list of valid page ranges for a page blob or snapshot of a page blob.
103// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-page-ranges.
104func (pb PageBlobURL) GetPageRanges(ctx context.Context, offset int64, count int64, ac BlobAccessConditions) (*PageList, error) {
105	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
106	return pb.pbClient.GetPageRanges(ctx, nil, nil,
107		httpRange{offset: offset, count: count}.pointers(),
108		ac.LeaseAccessConditions.pointers(),
109		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
110}
111
112// GetPageRangesDiff gets the collection of page ranges that differ between a specified snapshot and this page blob.
113// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-page-ranges.
114func (pb PageBlobURL) GetPageRangesDiff(ctx context.Context, offset int64, count int64, prevSnapshot string, ac BlobAccessConditions) (*PageList, error) {
115	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
116	return pb.pbClient.GetPageRangesDiff(ctx, nil, nil, &prevSnapshot,
117		httpRange{offset: offset, count: count}.pointers(),
118		ac.LeaseAccessConditions.pointers(),
119		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag,
120		nil)
121}
122
123// Resize resizes the page blob to the specified size (which must be a multiple of 512).
124// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-blob-properties.
125func (pb PageBlobURL) Resize(ctx context.Context, size int64, ac BlobAccessConditions) (*PageBlobResizeResponse, error) {
126	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
127	return pb.pbClient.Resize(ctx, size, nil, ac.LeaseAccessConditions.pointers(),
128		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
129}
130
131// SetSequenceNumber sets the page blob's sequence number.
132func (pb PageBlobURL) UpdateSequenceNumber(ctx context.Context, action SequenceNumberActionType, sequenceNumber int64,
133	ac BlobAccessConditions) (*PageBlobUpdateSequenceNumberResponse, error) {
134	sn := &sequenceNumber
135	if action == SequenceNumberActionIncrement {
136		sn = nil
137	}
138	ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch := ac.ModifiedAccessConditions.pointers()
139	return pb.pbClient.UpdateSequenceNumber(ctx, action, nil,
140		ac.LeaseAccessConditions.pointers(), ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch,
141		sn, nil)
142}
143
144// StartIncrementalCopy begins an operation to start an incremental copy from one page blob's snapshot to this page blob.
145// The snapshot is copied such that only the differential changes between the previously copied snapshot are transferred to the destination.
146// The copied snapshots are complete copies of the original snapshot and can be read or copied from as usual.
147// For more information, see https://docs.microsoft.com/rest/api/storageservices/incremental-copy-blob and
148// https://docs.microsoft.com/en-us/azure/virtual-machines/windows/incremental-snapshots.
149func (pb PageBlobURL) StartCopyIncremental(ctx context.Context, source url.URL, snapshot string, ac BlobAccessConditions) (*PageBlobCopyIncrementalResponse, error) {
150	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
151	qp := source.Query()
152	qp.Set("snapshot", snapshot)
153	source.RawQuery = qp.Encode()
154	return pb.pbClient.CopyIncremental(ctx, source.String(), nil,
155		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
156}
157
158func (pr PageRange) pointers() *string {
159	endOffset := strconv.FormatInt(int64(pr.End), 10)
160	asString := fmt.Sprintf("bytes=%v-%s", pr.Start, endOffset)
161	return &asString
162}
163
164type PageBlobAccessConditions struct {
165	ModifiedAccessConditions
166	LeaseAccessConditions
167	SequenceNumberAccessConditions
168}
169
170// SequenceNumberAccessConditions identifies page blob-specific access conditions which you optionally set.
171type SequenceNumberAccessConditions struct {
172	// IfSequenceNumberLessThan ensures that the page blob operation succeeds
173	// only if the blob's sequence number is less than a value.
174	// IfSequenceNumberLessThan=0 means no 'IfSequenceNumberLessThan' header specified.
175	// IfSequenceNumberLessThan>0 means 'IfSequenceNumberLessThan' header specified with its value
176	// IfSequenceNumberLessThan==-1 means 'IfSequenceNumberLessThan' header specified with a value of 0
177	IfSequenceNumberLessThan int64
178
179	// IfSequenceNumberLessThanOrEqual ensures that the page blob operation succeeds
180	// only if the blob's sequence number is less than or equal to a value.
181	// IfSequenceNumberLessThanOrEqual=0 means no 'IfSequenceNumberLessThanOrEqual' header specified.
182	// IfSequenceNumberLessThanOrEqual>0 means 'IfSequenceNumberLessThanOrEqual' header specified with its value
183	// IfSequenceNumberLessThanOrEqual=-1 means 'IfSequenceNumberLessThanOrEqual' header specified with a value of 0
184	IfSequenceNumberLessThanOrEqual int64
185
186	// IfSequenceNumberEqual ensures that the page blob operation succeeds
187	// only if the blob's sequence number is equal to a value.
188	// IfSequenceNumberEqual=0 means no 'IfSequenceNumberEqual' header specified.
189	// IfSequenceNumberEqual>0 means 'IfSequenceNumberEqual' header specified with its value
190	// IfSequenceNumberEqual=-1 means 'IfSequenceNumberEqual' header specified with a value of 0
191	IfSequenceNumberEqual int64
192}
193
194// pointers is for internal infrastructure. It returns the fields as pointers.
195func (ac SequenceNumberAccessConditions) pointers() (snltoe *int64, snlt *int64, sne *int64) {
196	var zero int64 // Defaults to 0
197	switch ac.IfSequenceNumberLessThan {
198	case -1:
199		snlt = &zero
200	case 0:
201		snlt = nil
202	default:
203		snlt = &ac.IfSequenceNumberLessThan
204	}
205
206	switch ac.IfSequenceNumberLessThanOrEqual {
207	case -1:
208		snltoe = &zero
209	case 0:
210		snltoe = nil
211	default:
212		snltoe = &ac.IfSequenceNumberLessThanOrEqual
213	}
214	switch ac.IfSequenceNumberEqual {
215	case -1:
216		sne = &zero
217	case 0:
218		sne = nil
219	default:
220		sne = &ac.IfSequenceNumberEqual
221	}
222	return
223}
224